diff --git a/jpos/src/main/java/org/jpos/iso/ISOBinaryFieldPackager.java b/jpos/src/main/java/org/jpos/iso/ISOBinaryFieldPackager.java index b2ed4f3f07..9c4f96ee21 100644 --- a/jpos/src/main/java/org/jpos/iso/ISOBinaryFieldPackager.java +++ b/jpos/src/main/java/org/jpos/iso/ISOBinaryFieldPackager.java @@ -18,6 +18,9 @@ package org.jpos.iso; +import org.jpos.iso.packager.GenericPackagerParams; +import org.xml.sax.Attributes; + import java.io.IOException; import java.io.InputStream; @@ -26,7 +29,7 @@ * @author joconnor * @version $Revision$ $Date$ */ -public class ISOBinaryFieldPackager extends ISOFieldPackager +public class ISOBinaryFieldPackager extends ISOFieldPackager implements GenericPackagerParams { private BinaryInterpreter interpreter; private Prefixer prefixer; @@ -88,6 +91,22 @@ public void setPrefixer(Prefixer prefixer) this.prefixer = prefixer; } + /** + * Supports the {@code inclusive="true"} GenericPackager attribute for + * one- and two-byte binary length prefixes. + */ + @Override + public void setGenericPackagerParams(Attributes atts) { + if (!Boolean.parseBoolean(atts.getValue("inclusive"))) + return; + if (prefixer == BinaryPrefixer.B) + prefixer = SelfInclusiveBinaryPrefixer.B; + else if (prefixer == BinaryPrefixer.BB) + prefixer = SelfInclusiveBinaryPrefixer.BB; + else + throw new IllegalArgumentException("The inclusive attribute requires a binary length prefix"); + } + public int getMaxPackedLength() { return prefixer.getPackedLength() + interpreter.getPackedLength(getLength()); diff --git a/jpos/src/main/java/org/jpos/iso/SelfInclusiveBinaryPrefixer.java b/jpos/src/main/java/org/jpos/iso/SelfInclusiveBinaryPrefixer.java new file mode 100644 index 0000000000..556b8d11c7 --- /dev/null +++ b/jpos/src/main/java/org/jpos/iso/SelfInclusiveBinaryPrefixer.java @@ -0,0 +1,61 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2026 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.iso; + +/** + * Binary prefixer whose encoded length includes the length prefix itself. + */ +public class SelfInclusiveBinaryPrefixer implements Prefixer { + /** A self-inclusive, one-byte binary length prefixer. */ + public static final SelfInclusiveBinaryPrefixer B = new SelfInclusiveBinaryPrefixer(BinaryPrefixer.B); + + /** A self-inclusive, two-byte binary length prefixer. */ + public static final SelfInclusiveBinaryPrefixer BB = new SelfInclusiveBinaryPrefixer(BinaryPrefixer.BB); + + private final BinaryPrefixer prefixer; + + private SelfInclusiveBinaryPrefixer(BinaryPrefixer prefixer) { + this.prefixer = prefixer; + } + + @Override + public void encodeLength(int length, byte[] b) throws ISOException { + int inclusiveLength = length + getPackedLength(); + if (inclusiveLength > maxLength()) + throw new ISOException("invalid len " + length + " for a self-inclusive binary prefix"); + prefixer.encodeLength(inclusiveLength, b); + } + + @Override + public int decodeLength(byte[] b, int offset) throws ISOException { + int inclusiveLength = prefixer.decodeLength(b, offset); + if (inclusiveLength < getPackedLength()) + throw new ISOException("invalid self-inclusive binary prefix " + inclusiveLength); + return inclusiveLength - getPackedLength(); + } + + @Override + public int getPackedLength() { + return prefixer.getPackedLength(); + } + + private int maxLength() { + return getPackedLength() == 1 ? 0xFF : 0xFFFF; + } +} diff --git a/jpos/src/main/java/org/jpos/iso/packager/GenericPackager.java b/jpos/src/main/java/org/jpos/iso/packager/GenericPackager.java index 6aef9a9124..61f4e5e149 100644 --- a/jpos/src/main/java/org/jpos/iso/packager/GenericPackager.java +++ b/jpos/src/main/java/org/jpos/iso/packager/GenericPackager.java @@ -481,6 +481,8 @@ else if (localName.equals("isofield")) f.setLength(Integer.parseInt(size)); f.setPad(Boolean.parseBoolean(pad)); f.setTrim(Boolean.parseBoolean(trim)); + if (f instanceof GenericPackagerParams) + ((GenericPackagerParams)f).setGenericPackagerParams(atts); // Modified for using TaggedFieldPackager if( f instanceof TaggedFieldPackager){ ((TaggedFieldPackager)f).setToken( token ); diff --git a/jpos/src/main/resources/org/jpos/iso/packager/genericpackager.dtd b/jpos/src/main/resources/org/jpos/iso/packager/genericpackager.dtd index 3029cfcf44..56187a0315 100644 --- a/jpos/src/main/resources/org/jpos/iso/packager/genericpackager.dtd +++ b/jpos/src/main/resources/org/jpos/iso/packager/genericpackager.dtd @@ -19,6 +19,7 @@ + @@ -39,3 +40,4 @@ + diff --git a/jpos/src/test/java/org/jpos/iso/SelfInclusiveBinaryPrefixerTest.java b/jpos/src/test/java/org/jpos/iso/SelfInclusiveBinaryPrefixerTest.java new file mode 100644 index 0000000000..cdc85f28f9 --- /dev/null +++ b/jpos/src/test/java/org/jpos/iso/SelfInclusiveBinaryPrefixerTest.java @@ -0,0 +1,43 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2026 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.iso; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SelfInclusiveBinaryPrefixerTest { + @Test + void encodesAndDecodesOneByteLengths() throws Exception { + byte[] prefix = new byte[1]; + SelfInclusiveBinaryPrefixer.B.encodeLength(10, prefix); + + assertEquals(11, prefix[0] & 0xFF); + assertEquals(10, SelfInclusiveBinaryPrefixer.B.decodeLength(prefix, 0)); + } + + @Test + void encodesAndDecodesTwoByteLengths() throws Exception { + byte[] prefix = new byte[2]; + SelfInclusiveBinaryPrefixer.BB.encodeLength(10, prefix); + + assertEquals(12, (prefix[0] & 0xFF) * 256 + (prefix[1] & 0xFF)); + assertEquals(10, SelfInclusiveBinaryPrefixer.BB.decodeLength(prefix, 0)); + } +} diff --git a/jpos/src/test/java/org/jpos/iso/packager/GenericPackagerTest.java b/jpos/src/test/java/org/jpos/iso/packager/GenericPackagerTest.java index 96eb24a7e3..6f8f1468ff 100644 --- a/jpos/src/test/java/org/jpos/iso/packager/GenericPackagerTest.java +++ b/jpos/src/test/java/org/jpos/iso/packager/GenericPackagerTest.java @@ -18,6 +18,7 @@ package org.jpos.iso.packager; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; @@ -30,6 +31,7 @@ import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; +import java.nio.charset.StandardCharsets; import java.util.EmptyStackException; import org.jpos.core.Configuration; @@ -39,6 +41,7 @@ import org.jpos.iso.IFA_AMOUNT; import org.jpos.iso.IFA_BITMAP; import org.jpos.iso.ISOException; +import org.jpos.iso.ISOBinaryField; import org.jpos.iso.ISOFieldPackager; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -51,6 +54,23 @@ public class GenericPackagerTest { + @Test + public void testInclusiveBinaryField() throws Exception { + String xml = "" + + "" + + "" + + ""; + GenericPackager packager = new GenericPackager(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); + + ISOFieldPackager fieldPackager = packager.getFieldPackager(126); + byte[] packed = fieldPackager.pack(new ISOBinaryField(126, new byte[] { 0x12, 0x34 })); + assertEquals(3, packed[0] & 0xFF); + + ISOBinaryField unpacked = new ISOBinaryField(126); + assertEquals(3, fieldPackager.unpack(unpacked, packed, 0)); + assertArrayEquals(new byte[] { 0x12, 0x34 }, (byte[]) unpacked.getValue()); + } + @Test public void testConstructor() throws Throwable { GenericPackager genericPackager = new GenericPackager();