diff --git a/pom.xml b/pom.xml index a6e9c5b..cd3cd41 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.solab j8583 - 1.3.4 + 1.4.0 jar j8583 Java implementation of the ISO 8583 protocol, focused on making the creation, edition and reading of ISO8583 messages as simple and flexible as possible. diff --git a/src/main/java/com/solab/iso8583/IsoType.java b/src/main/java/com/solab/iso8583/IsoType.java index 44b71c3..a89fa97 100644 --- a/src/main/java/com/solab/iso8583/IsoType.java +++ b/src/main/java/com/solab/iso8583/IsoType.java @@ -131,8 +131,15 @@ public enum IsoType { return value.substring(0, length); } char[] c = new char[length]; - System.arraycopy(value.toCharArray(), 0, c, 0, value.length()); - for (int i = value.length(); i < c.length; i++) { + int end = value.length(); + if (value.length() % 2 == 1) { + c[0] = '0'; + System.arraycopy(value.toCharArray(), 0, c, 1, value.length()); + end++; + } else { + System.arraycopy(value.toCharArray(), 0, c, 0, value.length()); + } + for (int i = end; i < c.length; i++) { c[i] = '0'; } return new String(c); diff --git a/src/main/java/com/solab/iso8583/IsoValue.java b/src/main/java/com/solab/iso8583/IsoValue.java index e3ac609..595fd8d 100644 --- a/src/main/java/com/solab/iso8583/IsoValue.java +++ b/src/main/java/com/solab/iso8583/IsoValue.java @@ -184,13 +184,14 @@ public class IsoValue implements Cloneable { if (value instanceof byte[]) { return type.format(encoder == null ? HexCodec.hexEncode((byte[])value) : encoder.encodeField(value), length * 2); } else { - return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length); + return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length * 2); } } else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) { if (value instanceof byte[]) { return encoder == null ? HexCodec.hexEncode((byte[])value) : encoder.encodeField(value); } else { - return encoder == null ? value.toString() : encoder.encodeField(value); + String _s = encoder == null ? value.toString() : encoder.encodeField(value); + return (_s.length() % 2 == 1) ? String.format("0%s", _s) : _s; } } return encoder == null ? value.toString() : encoder.encodeField(value); @@ -256,16 +257,17 @@ public class IsoValue implements Cloneable { //BCD encode the rest of the length outs.write((((length % 100) / 10) << 4) | (length % 10)); } else { + int _l = length*2; //write the length in ASCII if (type == IsoType.LLLBIN) { - outs.write((length / 50) + 48); + outs.write((_l / 100) + 48); } - if (length >= 10) { - outs.write((((length*2) % 100) / 10) + 48); + if (_l >= 10) { + outs.write(((_l % 100) / 10) + 48); } else { outs.write(48); } - outs.write(((length*2) % 10) + 48); + outs.write((_l % 10) + 48); } } else if (binary) { //numeric types in binary are coded like this @@ -285,10 +287,19 @@ public class IsoValue implements Cloneable { } } if (binary && (type == IsoType.BINARY || type == IsoType.LLBIN || type == IsoType.LLLBIN)) { + int missing = 0; if (value instanceof byte[]) { outs.write((byte[])value); + missing = length - ((byte[])value).length; } else { - outs.write(HexCodec.hexDecode(value.toString())); + byte[] binval = HexCodec.hexDecode(value.toString()); + outs.write(binval); + missing = length - binval.length; + } + if (type == IsoType.BINARY && missing > 0) { + for (int i = 0; i < missing; i++) { + outs.write(0); + } } } else { //Just write the value as text diff --git a/src/main/java/com/solab/iso8583/MessageFactory.java b/src/main/java/com/solab/iso8583/MessageFactory.java index 40ca272..bfae802 100644 --- a/src/main/java/com/solab/iso8583/MessageFactory.java +++ b/src/main/java/com/solab/iso8583/MessageFactory.java @@ -323,16 +323,19 @@ public class MessageFactory { } else { IsoValue val = fpi.parseBinary(buf, pos, getCustomField(i)); m.setField(i, val); - if (!(val.getType() == IsoType.ALPHA || val.getType() == IsoType.LLVAR - || val.getType() == IsoType.LLLVAR)) { - pos += (val.getLength() / 2) + (val.getLength() % 2); - } else { - pos += val.getLength(); - } - if (val.getType() == IsoType.LLVAR) { - pos++; - } else if (val.getType() == IsoType.LLLVAR) { - pos += 2; + if (val != null) { + if (val.getType() == IsoType.NUMERIC || val.getType() == IsoType.DATE10 + || val.getType() == IsoType.DATE4 || val.getType() == IsoType.DATE_EXP + || val.getType() == IsoType.AMOUNT || val.getType() == IsoType.TIME) { + pos += (val.getLength() / 2) + (val.getLength() % 2); + } else { + pos += val.getLength(); + } + if (val.getType() == IsoType.LLVAR || val.getType() == IsoType.LLBIN) { + pos++; + } else if (val.getType() == IsoType.LLLVAR || val.getType() == IsoType.LLLBIN) { + pos += 2; + } } } } @@ -348,9 +351,13 @@ public class MessageFactory { IsoValue val = fpi.parse(buf, pos, getCustomField(i)); m.setField(i, val); pos += val.getLength(); - if (val.getType() == IsoType.LLVAR) { + if (val.getType() == IsoType.LLBIN || val.getType() == IsoType.LLLBIN || val.getType() == IsoType.BINARY) { + //Binary types in ASCII are twice as long as they say they are (because of the hex encoding) + pos += val.getLength(); + } + if (val.getType() == IsoType.LLVAR || val.getType() == IsoType.LLBIN) { pos += 2; - } else if (val.getType() == IsoType.LLLVAR) { + } else if (val.getType() == IsoType.LLLVAR || val.getType() == IsoType.LLLBIN) { pos += 3; } } diff --git a/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java b/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java index 4233fd4..f783649 100644 --- a/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java @@ -27,6 +27,7 @@ import java.util.Date; import com.solab.iso8583.CustomField; import com.solab.iso8583.IsoType; import com.solab.iso8583.IsoValue; +import com.solab.iso8583.util.HexCodec; /** This class contains the information needed to parse a field from a message buffer. * @@ -87,18 +88,20 @@ public class FieldParseInfo { if (pos+2 > buf.length || length+pos+2 > buf.length) { throw new ParseException(String.format("Insufficient data for LLVAR field, pos %d", pos), pos); } + String _v = length == 0 ? "" : new String(buf, pos + 2, length); if (custom == null) { - return new IsoValue(type, new String(buf, pos + 2, length), length, null); + return new IsoValue(type, _v, length, null); } else { - IsoValue v = new IsoValue(type, custom.decodeField(new String(buf, pos + 2, length)), length, custom); + IsoValue v = new IsoValue(type, custom.decodeField(_v), length, custom); if (v.getValue() == null) { - return new IsoValue(type, new String(buf, pos + 2, length), length, null); + return new IsoValue(type, _v, length, null); } return v; } } else if (type == IsoType.LLLVAR) { if (!(Character.isDigit(buf[pos]) && Character.isDigit(buf[pos+1]) && Character.isDigit(buf[pos+2]))) { - throw new ParseException(String.format("Invalid LLLVAR length '%s' pos %d", new String(buf, pos, 3), pos), pos); + throw new ParseException(String.format("Invalid LLLVAR length '%s' pos %d", + new String(buf, pos, 3), pos), pos); } length = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48); if (length < 0) { @@ -123,12 +126,9 @@ public class FieldParseInfo { throw new ParseException(String.format("Insufficient data for AMOUNT field of length %d, pos %d", length, pos), pos); } - byte[] c = new byte[13]; - System.arraycopy(buf, pos, c, 0, 10); - System.arraycopy(buf, pos + 10, c, 11, 2); - c[10] = '.'; + String c = new String(buf, pos, 12); try { - return new IsoValue(type, new BigDecimal(new String(c)), null); + return new IsoValue(type, new BigDecimal(c).movePointLeft(2), null); } catch (NumberFormatException ex) { throw new ParseException(String.format("Cannot read amount '%s' pos %d", new String(c), pos), pos); } @@ -183,6 +183,69 @@ public class FieldParseInfo { cal.set(Calendar.MINUTE, ((buf[pos + 2] - 48) * 10) + buf[pos + 3] - 48); cal.set(Calendar.SECOND, ((buf[pos + 4] - 48) * 10) + buf[pos + 5] - 48); return new IsoValue(type, cal.getTime(), null); + + } else if (type == IsoType.BINARY) { + + if (pos+(length*2) > buf.length) { + throw new ParseException(String.format("Insufficient data for BINARY field of length %d, pos %d", + length, pos), pos); + } + byte[] binval = HexCodec.hexDecode(new String(buf, pos, length*2)); + if (custom == null) { + return new IsoValue(type, binval, binval.length, null); + } else { + IsoValue v = new IsoValue(type, custom.decodeField(new String(buf, pos, length*2)), length, custom); + if (v.getValue() == null) { + return new IsoValue(type, binval, binval.length, null); + } + return v; + } + + } else if (type == IsoType.LLBIN) { + + length = ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48); + if (length < 0) { + throw new ParseException(String.format("Invalid LLBIN length %d pos %d", length, pos), pos); + } + if (pos+2 > buf.length || length+pos+2 > buf.length) { + throw new ParseException(String.format("Insufficient data for LLBIN field, pos %d", pos), pos); + } + byte[] binval = length == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 2, length)); + if (custom == null) { + return new IsoValue(type, binval, binval.length, null); + } else { + IsoValue v = new IsoValue(type, custom.decodeField( + new String(buf, pos + 2, length)), binval.length, custom); + if (v.getValue() == null) { + return new IsoValue(type, binval, binval.length, null); + } + return v; + } + + } else if (type == IsoType.LLLBIN) { + + if (!(Character.isDigit(buf[pos]) && Character.isDigit(buf[pos+1]) && Character.isDigit(buf[pos+2]))) { + throw new ParseException(String.format("Invalid LLLBIN length '%s' pos %d", new String(buf, pos, 3), pos), pos); + } + length = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48); + if (length < 0) { + throw new ParseException(String.format("Invalid LLLBIN length %d pos %d", length, pos), pos); + } + if (length+pos+3 > buf.length) { + throw new ParseException(String.format("Insufficient data for LLLBIN field, pos %d", pos), pos); + } + byte[] binval = length == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 3, length)); + if (custom == null) { + return new IsoValue(type, binval, binval.length, null); + } else { + IsoValue v = new IsoValue(type, custom.decodeField( + length == 0 ? "" : new String(buf, pos + 3, length)), length, custom); + if (v.getValue() == null) { + //problems decoding? return the string + return new IsoValue(type, binval, binval.length, null); + } + return v; + } } return null; } @@ -229,6 +292,12 @@ public class FieldParseInfo { } else if (type == IsoType.LLVAR) { length = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f); + if (length < 0) { + throw new ParseException(String.format("Invalid LLVAR length %d pos %d", length, pos), pos); + } + if (pos+1 > buf.length || length+pos+1 > buf.length) { + throw new ParseException(String.format("Insufficient data for LLVAR field, pos %d", pos), pos); + } if (custom == null) { return new IsoValue(type, new String(buf, pos + 1, length), null); } else { @@ -242,6 +311,12 @@ public class FieldParseInfo { } else if (type == IsoType.LLLVAR) { length = ((buf[pos] & 0x0f) * 100) + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f); + if (length < 0) { + throw new ParseException(String.format("Invalid LLLVAR length %d pos %d", length, pos), pos); + } + if (length+pos+2 > buf.length) { + throw new ParseException(String.format("Insufficient data for LLLVAR field, pos %d", pos), pos); + } if (custom == null) { return new IsoValue(type, new String(buf, pos + 2, length), null); } else { @@ -264,7 +339,11 @@ public class FieldParseInfo { start++; } } - return new IsoValue(IsoType.AMOUNT, new BigDecimal(new String(digits)), null); + try { + return new IsoValue(IsoType.AMOUNT, new BigDecimal(new String(digits)), null); + } catch (NumberFormatException ex) { + throw new ParseException(String.format("Cannot read amount '%s' pos %d", new String(digits), pos), pos); + } } else if (type == IsoType.DATE10 || type == IsoType.DATE4 || type == IsoType.DATE_EXP || type == IsoType.TIME) { @@ -315,6 +394,62 @@ public class FieldParseInfo { return new IsoValue(type, cal.getTime(), null); } return new IsoValue(type, cal.getTime(), null); + } else if (type == IsoType.BINARY) { + + byte[] _v = new byte[length]; + System.arraycopy(buf, pos, _v, 0, length); + if (custom == null) { + return new IsoValue(type, _v, length, null); + } else { + IsoValue v = new IsoValue(type, custom.decodeField(HexCodec.hexEncode(_v)), length, custom); + if (v.getValue() == null) { + return new IsoValue(type, _v, length, null); + } + return v; + } + + } else if (type == IsoType.LLBIN) { + + length = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f); + if (length < 0) { + throw new ParseException(String.format("Invalid LLBIN length %d pos %d", length, pos), pos); + } + if (pos+1 > buf.length || length+pos+1 > buf.length) { + throw new ParseException(String.format("Insufficient data for LLBIN field, pos %d", pos), pos); + } + byte[] _v = new byte[length]; + System.arraycopy(buf, pos+1, _v, 0, length); + if (custom == null) { + return new IsoValue(type, _v, null); + } else { + IsoValue v = new IsoValue(type, custom.decodeField(HexCodec.hexEncode(_v)), custom); + if (v.getValue() == null) { + return new IsoValue(type, _v, null); + } + return v; + } + + } else if (type == IsoType.LLLBIN) { + + length = ((buf[pos] & 0x0f) * 100) + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f); + if (length < 0) { + throw new ParseException(String.format("Invalid LLLBIN length %d pos %d", length, pos), pos); + } + if (length+pos+2 > buf.length) { + throw new ParseException(String.format("Insufficient data for LLLBIN field, pos %d", pos), pos); + } + byte[] _v = new byte[length]; + System.arraycopy(buf, pos+2, _v, 0, length); + if (custom == null) { + return new IsoValue(type, _v, null); + } else { + IsoValue v = new IsoValue(type, custom.decodeField(HexCodec.hexEncode(_v)), custom); + if (v.getValue() == null) { + return new IsoValue(type, _v, null); + } + return v; + } + } return null; } diff --git a/src/main/java/com/solab/iso8583/util/HexCodec.java b/src/main/java/com/solab/iso8583/util/HexCodec.java index 077e69a..9c9311c 100644 --- a/src/main/java/com/solab/iso8583/util/HexCodec.java +++ b/src/main/java/com/solab/iso8583/util/HexCodec.java @@ -39,7 +39,7 @@ public class HexCodec { int holder = 0; int pos = 0; for (int i = 0; i < buf.length; i++) { - for (int z = 0; z < 2; z++) { + for (int z = 0; z < 2 && pos= 'A' && c <= 'F') { c -= 55; diff --git a/src/test/java/j8583/TestBinaries.java b/src/test/java/j8583/TestBinaries.java index e4d3b99..803a28d 100644 --- a/src/test/java/j8583/TestBinaries.java +++ b/src/test/java/j8583/TestBinaries.java @@ -1,6 +1,9 @@ package j8583; import java.io.IOException; +import java.math.BigDecimal; +import java.text.ParseException; +import java.util.Arrays; import junit.framework.Assert; @@ -26,20 +29,57 @@ public class TestBinaries { mfactBin.setUseBinaryMessages(true); } + void testParsed(IsoMessage m) { + Assert.assertEquals(m.getType(), 0x600); + Assert.assertEquals(new BigDecimal("1234.00"), m.getObjectValue(4)); + Assert.assertTrue("No field 7!", m.hasField(7)); + Assert.assertEquals("Wrong trace", "000123", m.getField(11).toString()); + byte[] buf = (byte[])m.getObjectValue(41); + byte[] exp = new byte[]{ (byte)0xab, (byte)0xcd, (byte)0xef, 0, 0, 0, 0, 0}; + Assert.assertEquals("Field 41 wrong length", 8, buf.length); + Assert.assertTrue("Field 41 wrong value", Arrays.equals(exp, buf)); + buf = (byte[])m.getObjectValue(42); + exp = new byte[]{ (byte)0x0a, (byte)0xbc, (byte)0xde, 0 }; + Assert.assertEquals("field 42 wrong length", 4, buf.length); + Assert.assertTrue("Field 42 wrong value", Arrays.equals(exp, buf)); + Assert.assertTrue(((String)m.getObjectValue(43)).startsWith("Field of length 40")); + buf = (byte[])m.getObjectValue(62); + exp = new byte[]{ 1, (byte)0x23, (byte)0x45, (byte)0x67, (byte)0x89, (byte)0xab, (byte)0xcd, (byte)0xef, + 1, (byte)0x23, (byte)0x45, (byte)0x67, (byte)0x89, (byte)0xab, (byte)0xcd, (byte)0xef }; + Assert.assertEquals(16, buf.length); + Assert.assertTrue(Arrays.equals(exp, buf)); + buf = (byte[])m.getObjectValue(64); + Assert.assertEquals(16, buf.length); + Assert.assertTrue(Arrays.equals(exp, buf)); + buf = (byte[])m.getObjectValue(63); + exp = new byte[]{ 0, (byte)0x12, (byte)0x34, (byte)0x56, (byte)0x78, (byte)0x9a }; + Assert.assertEquals(6, buf.length); + Assert.assertTrue(Arrays.equals(exp, buf)); + buf = (byte[])m.getObjectValue(65); + Assert.assertEquals(6, buf.length); + Assert.assertTrue(Arrays.equals(exp, buf)); + } + @Test - public void testMessages() { + public void testMessages() throws ParseException { //Create a message with both factories IsoMessage ascii = mfactAscii.newMessage(0x600); IsoMessage bin = mfactBin.newMessage(0x600); //HEXencode the binary message, headers should be similar to the ASCII version String hexBin = HexCodec.hexEncode(bin.writeData()); - String hexAscii = new String(ascii.writeData()); + String hexAscii = new String(ascii.writeData()).toUpperCase(); System.out.printf("bin: %s%nascii: %s%n", hexBin, hexAscii); Assert.assertEquals("0600", hexBin.substring(0, 4)); - //Should be the same up to the trace (first 64 chars) - Assert.assertEquals(hexAscii.substring(0, 64), hexBin.substring(0, 64)); + //Should be the same up to the field 42 (first 80 chars) + Assert.assertEquals(hexAscii.substring(0, 88), hexBin.substring(0, 88)); //Parse both messages + IsoMessage ascii2 = mfactAscii.parseMessage(ascii.writeData(), 0); + testParsed(ascii2); + Assert.assertEquals(ascii.getObjectValue(7).toString(), ascii2.getObjectValue(7).toString()); + IsoMessage bin2 = mfactBin.parseMessage(bin.writeData(), 0); //Compare values, should be the same + testParsed(bin2); + Assert.assertEquals(bin.getObjectValue(7).toString(), bin2.getObjectValue(7).toString()); } } diff --git a/src/test/java/j8583/TestHexCodec.java b/src/test/java/j8583/TestHexCodec.java index 9ca2cfb..987c28f 100644 --- a/src/test/java/j8583/TestHexCodec.java +++ b/src/test/java/j8583/TestHexCodec.java @@ -35,6 +35,10 @@ public class TestHexCodec { Assert.assertEquals(0xab, (buf[5] & 0xff)); Assert.assertEquals(0xcd, (buf[6] & 0xff)); Assert.assertEquals(0xef, (buf[7] & 0xff)); + buf = HexCodec.hexDecode("ABC"); + Assert.assertEquals(0x0a, (buf[0] & 0xff)); + Assert.assertEquals(0xbc, (buf[1] & 0xff)); + encodeDecode("ABC"); } } diff --git a/src/test/resources/config.xml b/src/test/resources/config.xml index a198a0a..95fd0da 100644 --- a/src/test/resources/config.xml +++ b/src/test/resources/config.xml @@ -38,11 +38,15 @@ @@ -103,8 +107,12 @@ + + + +