ISOUtil: Byte Manipulation Helpers
ISOUtil is the Swiss Army knife of jPOS low-level byte manipulation. It lives in org.jpos.iso.ISOUtil and is a static utility class — no instantiation, all static methods.
BCD encoding and decoding
Binary Coded Decimal (BCD) packs two decimal digits into a single byte. The high nibble holds the first digit, the low nibble holds the second. So the string "1234" becomes 0x12 0x34.
str2bcd
// Convert decimal string to BCD bytes
byte[] bcd = ISOUtil.str2bcd("1234", false);
// Result: [0x12, 0x34]
byte[] bcd = ISOUtil.str2bcd("123", false); // odd length, right-padded
// Result: [0x12, 0x30]
byte[] bcd = ISOUtil.str2bcd("123", true); // odd length, left-padded
// Result: [0x01, 0x23]
The padLeft flag controls which end gets the zero nibble when the digit count is odd. IFB_NUMERIC with pad=false uses right-padding; pad=true uses left-padding.
// In-place variant — fills existing array at a given offset
// (used internally by ISOStringFieldPackager to avoid extra allocations)
ISOUtil.str2bcd("9900", false, buffer, 4);
bcd2str
// Convert BCD bytes back to a decimal string
String s = ISOUtil.bcd2str(new byte[]{0x12, 0x34}, 0, 4, false);
// Result: "1234"
String s = ISOUtil.bcd2str(new byte[]{0x01, 0x23}, 0, 3, true);
// Result: "123" (left-padded BCD, 3 significant digits)
Parameters: (byte[] b, int offset, int numDigits, boolean padLeft).
Hex encoding and decoding
hexString
// byte[] → hex string (uppercase)
String hex = ISOUtil.hexString(new byte[]{0x1A, 0x2B, 0x3C});
// Result: "1A2B3C"
// Subset
String hex = ISOUtil.hexString(data, 4, 8); // 8 bytes starting at offset 4
hexString is the most-used helper in log output. Every ISOBinaryField.dump() and most channel trace output uses it.
hex2byte
// hex string → byte[]
byte[] data = ISOUtil.hex2byte("1A2B3C");
// Result: [0x1A, 0x2B, 0x3C]
// Odd-length: auto left-pads with zero
byte[] data = ISOUtil.hex2byte("ABC"); // → hex2byte("0ABC")
// Result: [0x0A, 0xBC]
Used everywhere binary data crosses a text boundary — loading keys from config files, parsing track data, preparing MAC computation inputs.
dumpString
// byte[] → printable form; replaces non-printable bytes with '.'
String s = ISOUtil.dumpString(new byte[]{0x41, 0x00, 0x42});
// Result: "A.B"
Useful for debugging raw channel traffic without triggering encoding errors.
String padding
// Zero-pad on the left to reach target length
String s = ISOUtil.zeropad("42", 6); // "000042"
String s = ISOUtil.zeropad(42L, 6); // "000042" (long variant)
// Zero-pad on the right
String s = ISOUtil.zeropadRight("42", 6); // "420000"
// Space-pad on the right (for character fields)
String s = ISOUtil.strpad("hi", 6); // "hi "
// Left-pad with an arbitrary character
String s = ISOUtil.padleft("hi", 6, '*'); // "****hi"
// Right-pad with an arbitrary character
String s = ISOUtil.padright("hi", 6, '*'); // "hi****"
Unpadding
String s = ISOUtil.zeroUnPad("000042"); // "42"
String s = ISOUtil.blankUnPad("hi "); // "hi"
String s = ISOUtil.trim(" hello "); // "hello" (both sides)
trim on a String removes leading and trailing whitespace and non-printable characters:
public static String trim(String s) {
if (s == null) return null;
int start = 0, end = s.length() - 1;
while (start <= end && s.charAt(start) <= ' ') start++;
while (end >= start && s.charAt(end) <= ' ') end--;
return s.substring(start, end + 1);
}
Validation helpers
boolean ok = ISOUtil.isNumeric("12345", 10); // true — all decimal digits
boolean ok = ISOUtil.isNumeric("123X5", 10); // false
boolean ok = ISOUtil.isZero("000000"); // true
boolean ok = ISOUtil.isBlank(" "); // true
boolean ok = ISOUtil.isAlphaNumeric("ABC123"); // true
isNumeric(String s, int radix) is more general — pass radix=16 to validate hex strings.
BitSet ↔ byte array
Used internally by IFB_BITMAP and IFA_BITMAP but also directly useful when manipulating processing codes and response codes as bit fields.
bitSet2byte
// BitSet (1-based field numbers) → compact byte array
BitSet bs = new BitSet();
bs.set(2); // field 2 present
bs.set(4); // field 4 present
bs.set(11); // field 11 present
byte[] bitmap = ISOUtil.bitSet2byte(bs);
// Produces 8 bytes; bit 0 of each byte = highest-numbered field in that byte's range
The 1-based convention: bs.get(n+1) corresponds to ISO-8583 field n. Bit 0 is unused.
// Force a specific byte count (e.g. always emit 16 bytes for primary + secondary)
byte[] bitmap = ISOUtil.bitSet2byte(bs, 16);
byte2BitSet
// byte array → BitSet (reverse of above)
BitSet bs = ISOUtil.byte2BitSet(data, 0, 64); // primary bitmap only (64 bits)
BitSet bs = ISOUtil.byte2BitSet(data, 0, 128); // primary + secondary
// Incremental: add secondary to existing BitSet
ISOUtil.byte2BitSet(existing, secondaryBytes, 64); // bits 65-128
hex2BitSet / bitSet2String
// Parse a hex-ASCII bitmap string into a BitSet
BitSet bs = ISOUtil.hex2BitSet(new byte[]{'7','2','3','4',...}, 0, 64);
// Format a BitSet as hex string (for IFA_BITMAP)
String hex = ISOUtil.bitSet2String(bs); // "7234040000000000" etc.
XOR and byte arithmetic
Essential for cryptographic operations (PIN block verification, MAC computation, key management).
// Byte-level XOR of two arrays (result length = minimum of the two)
byte[] result = ISOUtil.xor(key1, key2);
// String hex XOR (convenience wrapper)
String result = ISOUtil.hexor("1234ABCD", "FFFF0000");
// result = "EDCBABCD"
// Integer ↔ byte array (big-endian, minimum bytes)
byte[] b = ISOUtil.int2byte(0x0200); // [0x02, 0x00]
byte[] b = ISOUtil.int2byte(42); // [0x2A] (one byte, value fits)
int v = ISOUtil.byte2int(new byte[]{0x02, 0x00}); // 512
// Concatenate two byte arrays
byte[] full = ISOUtil.concat(header, body);
// Slice (trim) a byte array to length bytes
byte[] trimmed = ISOUtil.trim(data, 8);
Amount and display formatting
// Format a long amount with implied decimal (cents → display)
String s = ISOUtil.formatAmount(100L, 12); // " 1.00"
String s = ISOUtil.formatAmount(12345L, 12); // " 123.45"
The length argument includes the decimal point character. Common for field 4 display in logs.
PAN masking
protect() masks the middle digits of a PAN or track data, leaving the BIN (first 6) and the last 4 visible:
String masked = ISOUtil.protect("4111111111111111");
// Result: "411111______1111" (default mask char is '_')
String masked = ISOUtil.protect("4111111111111111", '*');
// Result: "411111******1111"
// Track 2 format (BIN=641111, expiry and service code stay masked)
String masked = ISOUtil.protect("4111111111111111=2512101000000000");
// Result: "411111______1111=________________"
Always use protect() before logging anything that might contain card data. jPOS channel and packager logging passes PAN values through protect() automatically in the standard log formatters.
EBCDIC conversion
For networks using EBCDIC encoding (common in mainframe-connected acquirer systems):
// ASCII String → EBCDIC bytes
byte[] ebcdic = ISOUtil.asciiToEbcdic("HELLO");
// EBCDIC bytes → ASCII String
String ascii = ISOUtil.ebcdicToAscii(ebcdic);
// In-place conversion into existing buffer
ISOUtil.asciiToEbcdic("VALUE", targetBuffer, offset);
The EBCDIC charset used is IBM1047 (the standard EBCDIC code page for US English financial systems), available as ISOUtil.EBCDIC.
normalize()
// Escape XML special characters (suitable for embedding in log output)
String safe = ISOUtil.normalize("<field>");
// Result: "<field>"
Called by ISOField.dump() to produce safe XML log output. You rarely need to call this directly unless building a custom log formatter.
sleep()
// Thread sleep that wraps LockSupport.parkNanos() (more precise than Thread.sleep())
ISOUtil.sleep(1000); // 1 second
ISOUtil.sleep(0); // Thread.yield()
Used throughout jPOS wherever a precise sleep is needed (retry loops, heartbeat intervals). Avoids the InterruptedException boilerplate of Thread.sleep().