From 00c046881b2a22030e14c73e03a7e258a8da45e8 Mon Sep 17 00:00:00 2001 From: Enrique Zamudio Date: Thu, 19 Feb 2015 19:39:55 -0600 Subject: [PATCH] fix #36 LLLLVAR + LLLLBIN --- .../java/com/solab/iso8583/IsoMessage.java | 5 +- src/main/java/com/solab/iso8583/IsoType.java | 6 +- src/main/java/com/solab/iso8583/IsoValue.java | 53 ++++++--- .../com/solab/iso8583/MessageFactory.java | 11 +- .../parse/AlphaNumericFieldParseInfo.java | 6 +- .../solab/iso8583/parse/FieldParseInfo.java | 20 +++- .../solab/iso8583/parse/LlllbinParseInfo.java | 104 +++++++++++++++++- .../solab/iso8583/parse/LlllvarParseInfo.java | 90 ++++++++++++++- .../solab/iso8583/parse/LlvarParseInfo.java | 12 +- 9 files changed, 270 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/solab/iso8583/IsoMessage.java b/src/main/java/com/solab/iso8583/IsoMessage.java index 84ec3c8..b47a103 100644 --- a/src/main/java/com/solab/iso8583/IsoMessage.java +++ b/src/main/java/com/solab/iso8583/IsoMessage.java @@ -341,8 +341,7 @@ public class IsoMessage { return bs; } - /** This calls writeInternal(), allowing applications to get the byte buffer containing the - * message data, without the length header. */ + /** Writes the message to a memory stream and returns a byte array with the result. */ public byte[] writeData() { ByteArrayOutputStream bout = new ByteArrayOutputStream(); if (isoHeader != null) { @@ -461,6 +460,8 @@ public class IsoMessage { sb.append(String.format("%02d", desc.length())); } else if (v.getType() == IsoType.LLLBIN || v.getType() == IsoType.LLLVAR) { sb.append(String.format("%03d", desc.length())); + } else if (v.getType() == IsoType.LLLLBIN || v.getType() == IsoType.LLLLVAR) { + sb.append(String.format("%04d", desc.length())); } sb.append(desc); } diff --git a/src/main/java/com/solab/iso8583/IsoType.java b/src/main/java/com/solab/iso8583/IsoType.java index fed34b9..90291c7 100644 --- a/src/main/java/com/solab/iso8583/IsoType.java +++ b/src/main/java/com/solab/iso8583/IsoType.java @@ -56,7 +56,11 @@ public enum IsoType { /** Similar to LLVAR but holds byte arrays instead of strings. */ LLBIN(false, 0), /** Similar to LLLVAR but holds byte arrays instead of strings. */ - LLLBIN(false, 0); + LLLBIN(false, 0), + /** variable length with 4-digit header length. */ + LLLLVAR(false, 0), + /** variable length byte array with 4-digit header length. */ + LLLLBIN(false, 0); private boolean needsLen; private int length; diff --git a/src/main/java/com/solab/iso8583/IsoValue.java b/src/main/java/com/solab/iso8583/IsoValue.java index f10f5dd..60a5126 100644 --- a/src/main/java/com/solab/iso8583/IsoValue.java +++ b/src/main/java/com/solab/iso8583/IsoValue.java @@ -63,7 +63,7 @@ public class IsoValue implements Cloneable { encoder = custom; type = t; this.value = value; - if (type == IsoType.LLVAR || type == IsoType.LLLVAR) { + if (type == IsoType.LLVAR || type == IsoType.LLLVAR || type == IsoType.LLLLVAR) { if (custom == null) { length = value.toString().length(); } else { @@ -77,8 +77,10 @@ public class IsoValue implements Cloneable { throw new IllegalArgumentException("LLVAR can only hold values up to 99 chars"); } else if (t == IsoType.LLLVAR && length > 999) { throw new IllegalArgumentException("LLLVAR can only hold values up to 999 chars"); - } - } else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) { + } else if (t == IsoType.LLLLVAR && length > 9999) { + throw new IllegalArgumentException("LLLLVAR can only hold values up to 9999 chars"); + } + } else if (type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN) { if (custom == null) { if (value instanceof byte[]) { length = ((byte[])value).length; @@ -98,6 +100,8 @@ public class IsoValue implements Cloneable { throw new IllegalArgumentException("LLBIN can only hold values up to 99 chars"); } else if (t == IsoType.LLLBIN && length > 999) { throw new IllegalArgumentException("LLLBIN can only hold values up to 999 chars"); + } else if (t == IsoType.LLLLBIN && length > 9999) { + throw new IllegalArgumentException("LLLLBIN can only hold values up to 9999 chars"); } } else { length = type.getLength(); @@ -122,7 +126,7 @@ public class IsoValue implements Cloneable { encoder = custom; if (length == 0 && t.needsLength()) { throw new IllegalArgumentException(String.format("Length must be greater than zero for type %s (value '%s')", t, val)); - } else if (t == IsoType.LLVAR || t == IsoType.LLLVAR) { + } else if (t == IsoType.LLVAR || t == IsoType.LLLVAR || t == IsoType.LLLLVAR) { if (len == 0) { length = custom == null ? val.toString().length() : custom.encodeField(value).length(); } @@ -130,8 +134,10 @@ public class IsoValue implements Cloneable { throw new IllegalArgumentException("LLVAR can only hold values up to 99 chars"); } else if (t == IsoType.LLLVAR && length > 999) { throw new IllegalArgumentException("LLLVAR can only hold values up to 999 chars"); + } else if (t == IsoType.LLLLVAR && length > 9999) { + throw new IllegalArgumentException("LLLLVAR can only hold values up to 9999 chars"); } - } else if (t == IsoType.LLBIN || t == IsoType.LLLBIN) { + } else if (t == IsoType.LLBIN || t == IsoType.LLLBIN || t == IsoType.LLLLBIN) { if (len == 0) { if (custom == null) { length = ((byte[])val).length; @@ -146,6 +152,8 @@ public class IsoValue implements Cloneable { throw new IllegalArgumentException("LLBIN can only hold values up to 99 chars"); } else if (t == IsoType.LLLBIN && length > 999) { throw new IllegalArgumentException("LLLBIN can only hold values up to 999 chars"); + } else if (t == IsoType.LLLLBIN && length > 9999) { + throw new IllegalArgumentException("LLLLBIN can only hold values up to 9999 chars"); } } } @@ -202,7 +210,7 @@ public class IsoValue implements Cloneable { } } else if (type == IsoType.ALPHA) { return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length); - } else if (type == IsoType.LLVAR || type == IsoType.LLLVAR) { + } else if (type == IsoType.LLVAR || type == IsoType.LLLVAR || type == IsoType.LLLLVAR) { return encoder == null ? value.toString() : encoder.encodeField(value); } else if (value instanceof Date) { return type.format((Date)value, tz); @@ -213,7 +221,7 @@ public class IsoValue implements Cloneable { } else { return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length * 2); } - } else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) { + } else if (type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN) { if (value instanceof byte[]) { final byte[] _v = (byte[])value; return encoder == null ? HexCodec.hexEncode(_v, 0, _v.length) : encoder.encodeField(value); @@ -255,11 +263,21 @@ public class IsoValue implements Cloneable { return encoder; } - protected void writeLengthHeader(final int l, final OutputStream outs, final int digits, + protected void writeLengthHeader(final int l, final OutputStream outs, final IsoType type, final boolean binary, final boolean forceStringEncoding) throws IOException { + final int digits; + if (type == IsoType.LLLLBIN || type == IsoType.LLLLVAR) { + digits = 4; + } else if (type == IsoType.LLLBIN || type == IsoType.LLLVAR) { + digits = 3; + } else { + digits = 2; + } if (binary) { - if (digits == 3) { + if (digits == 4) { + outs.write((((l % 1000) / 100) << 4) | ((l % 1000)/100)); + } else if (digits == 3) { outs.write(l / 100); //00 to 09 automatically in BCD } //BCD encode the rest of the length @@ -271,11 +289,16 @@ public class IsoValue implements Cloneable { lhead = '0' + lhead; } else if (ldiff == 2) { lhead = "00" + lhead; + } else if (ldiff == 3) { + lhead = "000" + lhead; } outs.write(encoding == null ? lhead.getBytes():lhead.getBytes(encoding)); } else { //write the length in ASCII - if (digits == 3) { + if (digits == 4) { + outs.write((l/1000)+48); + outs.write(((l%1000)/100)+48); + } else if (digits == 3) { outs.write((l / 100) + 48); } if (l >= 10) { @@ -295,10 +318,10 @@ public class IsoValue implements Cloneable { * for variable-length fields to be done with the proper character encoding. When false, * the length headers are encoded as ASCII; this used to be the only behavior. */ public void write(final OutputStream outs, final boolean binary, final boolean forceStringEncoding) throws IOException { - if (type == IsoType.LLLVAR || type == IsoType.LLVAR) { - writeLengthHeader(length, outs, type == IsoType.LLLVAR ? 3 : 2, binary, forceStringEncoding); - } else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) { - writeLengthHeader(binary ? length : length*2, outs, type == IsoType.LLLBIN ? 3 : 2, binary, forceStringEncoding); + if (type == IsoType.LLLVAR || type == IsoType.LLVAR || type == IsoType.LLLLVAR) { + writeLengthHeader(length, outs, type, binary, forceStringEncoding); + } else if (type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN) { + writeLengthHeader(binary ? length : length*2, outs, type, binary, forceStringEncoding); } else if (binary) { //numeric types in binary are coded like this byte[] buf = null; @@ -316,7 +339,7 @@ public class IsoValue implements Cloneable { return; } } - if (binary && (type == IsoType.BINARY || type == IsoType.LLBIN || type == IsoType.LLLBIN)) { + if (binary && (type == IsoType.BINARY || type == IsoType.LLBIN || type == IsoType.LLLBIN || type == IsoType.LLLLBIN)) { int missing = 0; if (value instanceof byte[]) { outs.write((byte[])value); diff --git a/src/main/java/com/solab/iso8583/MessageFactory.java b/src/main/java/com/solab/iso8583/MessageFactory.java index e023e14..0f0404c 100644 --- a/src/main/java/com/solab/iso8583/MessageFactory.java +++ b/src/main/java/com/solab/iso8583/MessageFactory.java @@ -448,9 +448,10 @@ public class MessageFactory { } if (val.getType() == IsoType.LLVAR || val.getType() == IsoType.LLBIN) { pos++; - } else if (val.getType() == IsoType.LLLVAR || val.getType() == IsoType.LLLBIN) { - pos += 2; - } + } else if (val.getType() == IsoType.LLLVAR || val.getType() == IsoType.LLLBIN + || val.getType() == IsoType.LLLLVAR || val.getType() == IsoType.LLLLBIN) { + pos += 2; + } } } } @@ -475,7 +476,9 @@ public class MessageFactory { pos += 2; } else if (val.getType() == IsoType.LLLVAR || val.getType() == IsoType.LLLBIN) { pos += 3; - } + } else if (val.getType() == IsoType.LLLLVAR || val.getType() == IsoType.LLLLBIN) { + pos += 4; + } } } } diff --git a/src/main/java/com/solab/iso8583/parse/AlphaNumericFieldParseInfo.java b/src/main/java/com/solab/iso8583/parse/AlphaNumericFieldParseInfo.java index e3148e2..f05abf2 100644 --- a/src/main/java/com/solab/iso8583/parse/AlphaNumericFieldParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/AlphaNumericFieldParseInfo.java @@ -52,11 +52,11 @@ public abstract class AlphaNumericFieldParseInfo extends FieldParseInfo { _v = new String(buf, pos, buf.length-pos, getCharacterEncoding()).substring(0, length); } if (custom == null) { - return new IsoValue(type, _v, length, null); + return new IsoValue<>(type, _v, length, null); } else { T decoded = custom.decodeField(_v); - return decoded == null ? new IsoValue(type, _v, length, null) : - new IsoValue(type, decoded, length, custom); + return decoded == null ? new IsoValue<>(type, _v, length, null) : + new IsoValue<>(type, decoded, length, custom); } } catch (StringIndexOutOfBoundsException ex) { throw new ParseException(String.format( diff --git a/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java b/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java index ef201da..25fdc2f 100644 --- a/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/FieldParseInfo.java @@ -127,7 +127,11 @@ public abstract class FieldParseInfo { fpi = new NumericParseInfo(len); } else if (t == IsoType.TIME) { fpi = new TimeParseInfo(); - } + } else if (t == IsoType.LLLLVAR) { + fpi = new LlllvarParseInfo(); + } else if (t == IsoType.LLLLBIN) { + fpi = new LlllbinParseInfo(); + } if (fpi == null) { throw new IllegalArgumentException(String.format("Cannot parse type %s", t)); } @@ -138,11 +142,19 @@ public abstract class FieldParseInfo { protected int decodeLength(byte[] buf, int pos, int digits) throws UnsupportedEncodingException { if (forceStringDecoding) { return Integer.parseInt(new String(buf, pos, digits, encoding), 10); - } else if (digits == 3) { - return ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48); } else { - return ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48); + switch(digits) { + case 2: + return ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48); + case 3: + return ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + + (buf[pos + 2] - 48); + case 4: + return ((buf[pos] - 48) * 1000) + ((buf[pos + 1] - 48) * 100) + + ((buf[pos + 2] - 48) * 10) + (buf[pos + 3] - 48); + } } + return -1; } } diff --git a/src/main/java/com/solab/iso8583/parse/LlllbinParseInfo.java b/src/main/java/com/solab/iso8583/parse/LlllbinParseInfo.java index fdbcfe2..21b3204 100644 --- a/src/main/java/com/solab/iso8583/parse/LlllbinParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/LlllbinParseInfo.java @@ -1,10 +1,112 @@ package com.solab.iso8583.parse; +import com.solab.iso8583.CustomBinaryField; +import com.solab.iso8583.CustomField; +import com.solab.iso8583.IsoType; +import com.solab.iso8583.IsoValue; +import com.solab.iso8583.util.HexCodec; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; + /** * Blabla. * * @author Enrique Zamudio * Date: 19/02/15 18:29 */ -public class LlllbinParseInfo { +public class LlllbinParseInfo extends FieldParseInfo { + + + public LlllbinParseInfo() { + super(IsoType.LLLLBIN, 0); + } + + @Override + public IsoValue parse(final int field, final byte[] buf, + final int pos, final CustomField custom) + throws ParseException, UnsupportedEncodingException { + if (pos < 0) { + throw new ParseException(String.format("Invalid LLLLBIN field %d pos %d", + field, pos), pos); + } else if (pos+4 > buf.length) { + throw new ParseException(String.format("Insufficient LLLLBIN header field %d", + field), pos); + } + final int l = decodeLength(buf, pos, 4); + if (l < 0) { + throw new ParseException(String.format("Invalid LLLLBIN length %d field %d pos %d", + l, field, pos), pos); + } else if (l+pos+4 > buf.length) { + throw new ParseException(String.format( + "Insufficient data for LLLLBIN field %d, pos %d", field, pos), pos); + } + byte[] binval = l == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 4, l)); + if (custom == null) { + return new IsoValue<>(type, binval, binval.length, null); + } else if (custom instanceof CustomBinaryField) { + try { + T dec = ((CustomBinaryField)custom).decodeBinaryField( + buf, pos + 4, l); + return dec == null ? new IsoValue<>(type, binval, binval.length, null) : + new IsoValue<>(type, dec, 0, custom); + } catch (IndexOutOfBoundsException ex) { + throw new ParseException(String.format( + "Insufficient data for LLLLBIN field %d, pos %d", field, pos), pos); + } + } else { + try { + T dec = custom.decodeField( + l == 0 ? "" : new String(buf, pos + 4, l)); + return dec == null ? new IsoValue<>(type, binval, binval.length, null) : + new IsoValue<>(type, dec, l, custom); + } catch (IndexOutOfBoundsException ex) { + throw new ParseException(String.format( + "Insufficient data for LLLLBIN field %d, pos %d", field, pos), pos); + } + } + } + + @Override + public IsoValue parseBinary(final int field, final byte[] buf, + final int pos, final CustomField custom) + throws ParseException { + if (pos < 0) { + throw new ParseException(String.format("Invalid bin LLLLBIN field %d pos %d", + field, pos), pos); + } else if (pos+2 > buf.length) { + throw new ParseException(String.format("Insufficient LLLLBIN header field %d", + field), pos); + } + final int l = ((buf[pos] & 0xf0) * 1000) + ((buf[pos] & 0x0f) * 100) + + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f); + if (l < 0) { + throw new ParseException(String.format("Invalid LLLLBIN length %d field %d pos %d", + l, field, pos), pos); + } + if (l+pos+2 > buf.length) { + throw new ParseException(String.format( + "Insufficient data for bin LLLLBIN field %d, pos %d requires %d, only %d available", + field, pos, l, buf.length-pos+1), pos); + } + byte[] _v = new byte[l]; + System.arraycopy(buf, pos+2, _v, 0, l); + if (custom == null) { + return new IsoValue<>(type, _v, null); + } else if (custom instanceof CustomBinaryField) { + try { + T dec = ((CustomBinaryField)custom).decodeBinaryField( + buf, pos + 2, l); + return dec == null ? new IsoValue<>(type, _v, _v.length, null) : + new IsoValue(type, dec, l, custom); + } catch (IndexOutOfBoundsException ex) { + throw new ParseException(String.format( + "Insufficient data for LLLLBIN field %d, pos %d", field, pos), pos); + } + } else { + T dec = custom.decodeField(HexCodec.hexEncode(_v, 0, _v.length)); + return dec == null ? new IsoValue<>(type, _v, null) : + new IsoValue<>(type, dec, custom); + } + } } diff --git a/src/main/java/com/solab/iso8583/parse/LlllvarParseInfo.java b/src/main/java/com/solab/iso8583/parse/LlllvarParseInfo.java index 5f67ea0..0e415c4 100644 --- a/src/main/java/com/solab/iso8583/parse/LlllvarParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/LlllvarParseInfo.java @@ -1,10 +1,98 @@ package com.solab.iso8583.parse; +import com.solab.iso8583.CustomField; +import com.solab.iso8583.IsoType; +import com.solab.iso8583.IsoValue; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; + /** * Blabla. * * @author Enrique Zamudio * Date: 19/02/15 18:30 */ -public class LlllvarParseInfo { +public class LlllvarParseInfo extends FieldParseInfo { + + public LlllvarParseInfo() { + super(IsoType.LLLLVAR, 0); + } + + @Override + public IsoValue parse(final int field, final byte[] buf, + final int pos, final CustomField custom) + throws ParseException, UnsupportedEncodingException { + if (pos < 0) { + throw new ParseException(String.format( + "Invalid LLLLVAR field %d %d", field, pos), pos); + } else if (pos+4 > buf.length) { + throw new ParseException(String.format( + "Insufficient data for LLLLVAR header, pos %d", pos), pos); + } + final int len = decodeLength(buf, pos, 4); + if (len < 0) { + throw new ParseException(String.format( + "Invalid LLLLVAR length %d, field %d pos %d", len, field, pos), pos); + } else if (len+pos+4 > buf.length) { + throw new ParseException(String.format( + "Insufficient data for LLLLVAR field %d, pos %d", field, pos), pos); + } + String _v; + try { + _v = len == 0 ? "" : new String(buf, pos + 4, len, getCharacterEncoding()); + } catch (IndexOutOfBoundsException ex) { + throw new ParseException(String.format( + "Insufficient data for LLLLVAR header, field %d pos %d", field, pos), pos); + } + //This is new: if the String's length is different from the specified + // length in the buffer, there are probably some extended characters. + // So we create a String from the rest of the buffer, and then cut it to + // the specified length. + if (_v.length() != len) { + _v = new String(buf, pos + 4, buf.length-pos-4, + getCharacterEncoding()).substring(0, len); + } + if (custom == null) { + return new IsoValue<>(type, _v, len, null); + } else { + T dec = custom.decodeField(_v); + return dec == null ? new IsoValue<>(type, _v, len, null) : + new IsoValue<>(type, dec, len, custom); + } + } + + @Override + public IsoValue parseBinary(final int field, final byte[] buf, + final int pos, final CustomField custom) + throws ParseException, UnsupportedEncodingException { + if (pos < 0) { + throw new ParseException(String.format("Invalid bin LLLLVAR field %d pos %d", + field, pos), pos); + } else if (pos+2 > buf.length) { + throw new ParseException(String.format( + "Insufficient data for bin LLLLVAR header, field %d pos %d", + field, pos), pos); + } + final int len = ((buf[pos] & 0xf0) * 1000) + ((buf[pos] & 0x0f) * 100) + + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f); + if (len < 0) { + throw new ParseException(String.format( + "Invalid bin LLLLVAR length %d, field %d pos %d", len, field, pos), pos); + } + if (len+pos+2 > buf.length) { + throw new ParseException(String.format( + "Insufficient data for bin LLLLVAR field %d, pos %d", field, pos), pos); + } + if (custom == null) { + return new IsoValue<>(type, new String(buf, pos + 2, len, + getCharacterEncoding()), null); + } else { + T dec = custom.decodeField(new String(buf, pos + 2, len, getCharacterEncoding())); + return dec == null ? new IsoValue<>(type, + new String(buf, pos + 2, len, getCharacterEncoding()), null) : + new IsoValue<>(type, dec, custom); + } + } + } diff --git a/src/main/java/com/solab/iso8583/parse/LlvarParseInfo.java b/src/main/java/com/solab/iso8583/parse/LlvarParseInfo.java index 6841522..7662dd6 100644 --- a/src/main/java/com/solab/iso8583/parse/LlvarParseInfo.java +++ b/src/main/java/com/solab/iso8583/parse/LlvarParseInfo.java @@ -70,11 +70,11 @@ public class LlvarParseInfo extends FieldParseInfo { getCharacterEncoding()).substring(0, len); } if (custom == null) { - return new IsoValue(type, _v, len, null); + return new IsoValue<>(type, _v, len, null); } else { T dec = custom.decodeField(_v); - return dec == null ? new IsoValue(type, _v, len, null) : - new IsoValue(type, dec, len, custom); + return dec == null ? new IsoValue<>(type, _v, len, null) : + new IsoValue<>(type, dec, len, custom); } } @@ -100,13 +100,13 @@ public class LlvarParseInfo extends FieldParseInfo { "Insufficient data for bin LLVAR field %d, pos %d", field, pos), pos); } if (custom == null) { - return new IsoValue(type, new String(buf, pos + 1, len, + return new IsoValue<>(type, new String(buf, pos + 1, len, getCharacterEncoding()), null); } else { T dec = custom.decodeField(new String(buf, pos + 1, len, getCharacterEncoding())); - return dec == null ? new IsoValue(type, + return dec == null ? new IsoValue<>(type, new String(buf, pos + 1, len, getCharacterEncoding()), null) : - new IsoValue(type, dec, custom); + new IsoValue<>(type, dec, custom); } }