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 @@
- 000000123400
+ 1234
123
abcdef
+ abcde
Field of length 40
- 0123456789abcdef
+ 0123456789abcdef0123456789abcdef
+ 0123456789a
+ 0123456789abcdef0123456789abcdef
+ 0123456789a
Field of type LLLVAR
@@ -103,8 +107,12 @@
+
+
+
+