001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.tlv.packager.bertlv;
020
021
022import org.jpos.emv.EMVStandardTagType;
023import org.jpos.emv.UnknownTagNumberException;
024import org.jpos.iso.AsciiInterpreter;
025import org.jpos.iso.BCDInterpreter;
026import org.jpos.iso.BinaryInterpreter;
027import org.jpos.iso.ISOBinaryField;
028import org.jpos.iso.ISOComponent;
029import org.jpos.iso.ISOException;
030import org.jpos.iso.ISOField;
031import org.jpos.iso.ISOFieldPackager;
032import org.jpos.iso.ISOMsg;
033import org.jpos.iso.ISOUtil;
034import org.jpos.iso.Interpreter;
035import org.jpos.iso.LiteralInterpreter;
036import org.jpos.iso.packager.GenericPackager;
037import org.jpos.tlv.ISOTaggedField;
038import org.jpos.tlv.TLVDataFormat;
039import org.jpos.util.LogEvent;
040import org.jpos.util.Logger;
041
042import java.io.ByteArrayOutputStream;
043import java.io.IOException;
044import java.io.InputStream;
045import java.util.Map;
046
047
048/**
049 * Packager for ISO 8825 BER TLV values.
050 *
051 * @author Vishnu Pillai
052 */
053
054public abstract class BERTLVPackager extends GenericPackager {
055
056    private static final int MAX_LENGTH_BYTES = 5;
057    private static final int MAX_TAG_BYTES = 3;
058
059    private static final LiteralInterpreter literalInterpreter = LiteralInterpreter.INSTANCE;
060    private static final AsciiInterpreter asciiInterpreter = AsciiInterpreter.INSTANCE;
061    private static final BCDInterpreter bcdInterpreterLeftPaddedZero = BCDInterpreter.LEFT_PADDED;
062    private static final BCDInterpreter bcdInterpreterRightPaddedF = BCDInterpreter.RIGHT_PADDED_F;
063
064    private final BinaryInterpreter tagInterpreter;
065    private final BinaryInterpreter lengthInterpreter;
066    private final BinaryInterpreter valueInterpreter;
067
068
069    /**
070     * Default constructor.
071     * @throws ISOException on configuration error
072     */
073    public BERTLVPackager() throws ISOException {
074        super();
075        tagInterpreter = getTagInterpreter();
076        lengthInterpreter = getLengthInterpreter();
077        valueInterpreter = getValueInterpreter();
078    }
079
080    /**
081     * Returns the interpreter used for encoding/decoding tag bytes.
082     * @return tag byte interpreter
083     */
084    protected abstract BinaryInterpreter getTagInterpreter();
085
086    /**
087     * Returns the interpreter used for encoding/decoding length bytes.
088     * @return length byte interpreter
089     */
090    protected abstract BinaryInterpreter getLengthInterpreter();
091
092    /**
093     * Returns the interpreter used for encoding/decoding value bytes.
094     * @return value byte interpreter
095     */
096    protected abstract BinaryInterpreter getValueInterpreter();
097
098    /**
099     * Returns the format mapper for this packager's tag set.
100     * @return BER-TLV format mapper
101     */
102    protected abstract BERTLVFormatMapper getTagFormatMapper();
103
104    /**
105     * Pack the sub-field into a byte array
106     */
107    @Override
108    public byte[] pack(ISOComponent m) throws ISOException {
109        return pack(m, false, getFirstField(), m.getMaxField());
110    }
111
112    /**
113     * Packs a subset of sub-fields.
114     * @param m the ISO component to pack
115     * @param nested true if this is a nested (inner) pack
116     * @param startIdx first field index to include
117     * @param endIdx last field index to include
118     * @return packed bytes
119     * @throws ISOException on packing error
120     */
121    public byte[] pack(ISOComponent m, boolean nested, int startIdx, int endIdx)
122            throws ISOException {
123        LogEvent evt = new LogEvent(this, "pack");
124        try (ByteArrayOutputStream bout = new ByteArrayOutputStream(100)) {
125            ISOComponent c;
126            Map fields = m.getChildren();
127            for (int i = startIdx; i <= endIdx; i++) {
128                c = (ISOComponent) fields.get(i);
129                if (c != null) {
130                    try {
131                        final byte[] b;
132                        if (c instanceof ISOTaggedField) {
133                            b = packTLV((ISOTaggedField) c);
134                        } else {
135
136                            if (c.getValue() == null) {
137                                b = new byte[0];
138                            } else if (!nested && (i == startIdx || i == endIdx) &&
139                                    this.fld.length > i && this.fld[i] != null) {
140                                b = this.fld[i].pack(c);
141                            } else {
142                                throw new ISOException(
143                                        "Field: " +
144                                                i +
145                                                " of type: " +
146                                                c.getClass() +
147                                                " cannot be packed. Either the object should be of type ISOTagField" +
148                                                " OR this should be the first or last sub-field and a packager" +
149                                                " should be configured for the same");
150                            }
151                        }
152                        bout.write(b);
153                    } catch (Exception e) {
154                        evt.addMessage("error packing sub-field " + i);
155                        evt.addMessage(c);
156                        evt.addMessage(e);
157                        throw e;
158                    }
159                }
160            }
161
162            byte[] d = bout.toByteArray();
163            if (logger != null) // save a few CPU cycle if no logger available
164                evt.addMessage(ISOUtil.hexString(d));
165            return d;
166        } catch (ISOException e) {
167            evt.addMessage(e);
168            throw e;
169        } catch (Exception e) {
170            evt.addMessage(e);
171            throw new ISOException(e);
172        } finally {
173            Logger.log(evt);
174        }
175    }
176
177    private byte[] packTLV(ISOTaggedField c) throws ISOException {
178        byte[] b;
179        final byte[] rawValueBytes;
180
181        try {
182            rawValueBytes = packValue(c.getTag(), c);
183        } catch (UnknownTagNumberException e) {
184            throw new ISOException(e);
185        }
186
187        byte[] valueBytes = new byte[valueInterpreter.getPackedLength(rawValueBytes.length)];
188        valueInterpreter.interpret(rawValueBytes, valueBytes, 0);
189
190        byte[] tagBytes = packTag(c);
191        byte[] lengthBytes = packLength(valueBytes);
192
193        b = new byte[tagBytes.length + lengthBytes.length + valueBytes.length];
194        System.arraycopy(tagBytes, 0, b, 0, tagBytes.length);
195        System.arraycopy(lengthBytes, 0, b, tagBytes.length, lengthBytes.length);
196        System.arraycopy(valueBytes, 0, b, tagBytes.length + lengthBytes.length, valueBytes.length);
197        return b;
198    }
199
200    private byte[] packTag(final ISOTaggedField c) {
201        final byte[] tagBytes;
202        String tag = c.getTag();
203        tagBytes = ISOUtil.hex2byte(tag);
204        byte[] packedTagBytes = new byte[tagInterpreter.getPackedLength(tagBytes.length)];
205        tagInterpreter.interpret(tagBytes, packedTagBytes, 0);
206        return packedTagBytes;
207    }
208
209    /** @param valueBytes the value bytes to prefix
210     * @return BER-TLV length-encoded prefix bytes
211     */
212    private byte[] packLength(final byte[] valueBytes) {
213        final byte[] lengthBytes;
214        int length = valueBytes.length;
215        if (length > 0x7F) {
216            byte[] lengthBytesSuffix = ISOUtil.int2byte(length);
217            lengthBytes = new byte[lengthBytesSuffix.length + 1];
218            lengthBytes[0] = (byte) (0x80 | lengthBytesSuffix.length);
219            System.arraycopy(lengthBytesSuffix, 0, lengthBytes, 1, lengthBytesSuffix.length);
220        } else {
221            lengthBytes = new byte[]{(byte) length};
222        }
223        byte[] packedLengthBytes = new byte[lengthInterpreter.getPackedLength(lengthBytes.length)];
224        lengthInterpreter.interpret(lengthBytes, packedLengthBytes, 0);
225        return packedLengthBytes;
226
227    }
228
229    @Override
230    public int unpack(ISOComponent m, byte[] b) throws ISOException {
231        try {
232            return unpack(m, b, false);
233        } catch (RuntimeException e) {
234            throw new ISOException(e);
235        }
236    }
237
238    /**
239     * Unpacks BER-TLV encoded data.
240     * @param m target component
241     * @param b packed bytes
242     * @param nested true for nested unpack
243     * @return consumed bytes
244     * @throws ISOException on unpacking error
245     */
246    public int unpack(ISOComponent m, byte[] b, boolean nested) throws ISOException {
247        LogEvent evt = new LogEvent(this, "unpack");
248        try {
249            if (m.getComposite() == null)
250                throw new ISOException("Can't call packager on non Composite");
251            if (b.length == 0)
252                return 0; // nothing to do
253            if (logger != null) // save a few CPU cycle if no logger available
254                evt.addMessage(ISOUtil.hexString(b));
255
256            int tlvDataLength = b.length;
257
258            int consumed = 0;
259            int subFieldNumber = 1;
260            if (!nested && fld.length > 1) {
261                ISOFieldPackager packager = fld[1];
262                if (packager != null) {
263                    ISOComponent subField = packager.createComponent(1);
264                    consumed = consumed + packager.unpack(subField, b, consumed);
265                    m.set(subField);
266                }
267                subFieldNumber++;
268            }
269
270            while (consumed < tlvDataLength) {
271                ISOFieldPackager packager;
272                if (!nested && fld.length > 1 && (packager = fld[fld.length - 1]) != null &&
273                        packager.getLength() == tlvDataLength - consumed) {
274                    ISOComponent subField = packager.createComponent(fld.length - 1);
275                    consumed = consumed + packager.unpack(subField, b, consumed);
276                    m.set(subField);
277                    subFieldNumber++;
278                } else {
279                    //Read the Tag per BER
280                    UnpackResult tagUnpackResult = unpackTag(b, consumed);
281                    consumed = consumed + tagUnpackResult.consumed;
282                    final byte[] tagBytes = tagUnpackResult.value;
283                    String tag = ISOUtil.byte2hex(tagBytes).toUpperCase();
284                    UnpackResult lengthUnpackResult = unpackLength(b, consumed);
285                    consumed = consumed + lengthUnpackResult.consumed;
286                    int length = ISOUtil.byte2int(lengthUnpackResult.value);
287
288                    final ISOComponent tlvSubFieldData;
289                    byte[] value = new byte[length];
290
291                    if (length > 0) {
292                        System.arraycopy(b, consumed, value, 0, value.length);
293                    }
294
295                    int uninterpretLength = getUninterpretLength(length, valueInterpreter);
296                    byte[] rawValueBytes =
297                            valueInterpreter.uninterpret(value, 0, uninterpretLength);
298
299                    tlvSubFieldData = unpackValue(tag, rawValueBytes, subFieldNumber, length);
300                    consumed = consumed + length;
301                    ISOTaggedField tlv = new ISOTaggedField(tag, tlvSubFieldData);
302                    m.set(tlv);
303                    subFieldNumber++;
304                }
305            }
306            if (b.length != consumed) {
307                evt.addMessage("WARNING: unpack len=" + b.length + " consumed=" + consumed);
308            }
309            return consumed;
310        } catch (ISOException e) {
311            evt.addMessage(e);
312            throw e;
313        } catch (Exception e) {
314            evt.addMessage(e);
315            throw new ISOException(e);
316        } finally {
317            Logger.log(evt);
318        }
319    }
320
321    private UnpackResult unpackTag(final byte[] tlvData, final int offset) {
322        byte[] tlvBytesHex =
323                tagInterpreter.uninterpret(
324                        tlvData,
325                        offset,
326                        tlvData.length >= offset + MAX_TAG_BYTES
327                                ? MAX_TAG_BYTES : tlvData.length - offset);
328        int index = 0;
329        final byte[] tagBytes;
330        byte tagByte = tlvBytesHex[index];
331        int tagLength = 1;
332        if ((tagByte & 0x1F) == 0x1F) {
333            tagLength++;
334            tagByte = tlvBytesHex[index + 1];
335            while (/* tagLength < MAX_TAG_BYTES && */(tagByte & 0x80) == 0x80) {
336                tagLength++;
337                tagByte = tlvBytesHex[index + tagLength - 1];
338            }
339            tagBytes = new byte[tagLength];
340            System.arraycopy(tlvBytesHex, index, tagBytes, 0, tagBytes.length);
341        } else {
342            tagBytes = new byte[]{tagByte};
343        }
344        return new UnpackResult(tagBytes, tagInterpreter.getPackedLength(tagLength));
345    }
346
347    /** @param tlvData data bytes
348     * @param offset start offset
349     * @return UnpackResult containing length bytes and consumed size
350     */
351    private UnpackResult unpackLength(final byte[] tlvData, final int offset) {
352        byte[] tlvBytesHex =
353                lengthInterpreter.uninterpret(
354                        tlvData,
355                        offset,
356                        tlvData.length >= offset + MAX_LENGTH_BYTES
357                                ? MAX_LENGTH_BYTES : tlvData.length - offset);
358        final byte length = tlvBytesHex[0];
359        final int lengthLength;
360        final byte[] lengthBytes;
361        if ((length & 0x80) == 0x80) {
362            //Long Form
363            int lengthOctetsCount = length & 0x7F;
364            lengthLength = lengthOctetsCount + 1;
365            lengthBytes = new byte[lengthOctetsCount];
366            System.arraycopy(tlvBytesHex, 1, lengthBytes, 0, lengthOctetsCount);
367        } else {
368            //Short Form
369            lengthLength = 1;
370            lengthBytes = new byte[]{length};
371        }
372        return new UnpackResult(lengthBytes, lengthInterpreter.getPackedLength(lengthLength));
373    }
374
375    /**
376     * Packs a single TLV value field.
377     * Packs the value of the given component for the specified tag.
378     * @param tagNameHex the tag name as a hex string
379     * @param c the component to pack
380     * @return packed value bytes
381     * @throws ISOException on packing error
382     * @throws UnknownTagNumberException if the tag is not recognised
383     */
384    protected byte[] packValue(String tagNameHex, final ISOComponent c) throws ISOException,
385            UnknownTagNumberException {
386        final int tagNumber = Integer.parseInt(tagNameHex, 16);
387        final TLVDataFormat dataFormat = getTagFormatMapper().getFormat(tagNumber);
388        String tagValue;
389        byte[] packedValue;
390
391        if (c.getComposite() == null) {
392            if (c.getValue() instanceof String) {
393                tagValue = (String) c.getValue();
394                EMVStandardTagType tagType;
395                int length;
396                if (EMVStandardTagType.isProprietaryTag(Integer.parseInt(tagNameHex, 16))) {
397                    length = tagValue.length();
398                } else {
399                    tagType = EMVStandardTagType.forHexCode(tagNameHex);
400                    length = Math.max(tagValue.length(), tagType.getDataLength().getMinLength());
401                }
402                switch (dataFormat) {
403                    case COMPRESSED_NUMERIC:
404                        packedValue = new byte[bcdInterpreterRightPaddedF.getPackedLength(length)];
405                        bcdInterpreterRightPaddedF.interpret(tagValue, packedValue, 0);
406                        break;
407                    case PACKED_NUMERIC:
408                    case PACKED_NUMERIC_DATE_YYMMDD:
409                    case PACKED_NUMERIC_TIME_HHMMSS:
410                        packedValue = new byte[bcdInterpreterLeftPaddedZero.getPackedLength(length)];
411                        bcdInterpreterLeftPaddedZero.interpret(ISOUtil.zeropad(tagValue, length), packedValue, 0);
412                        break;
413                    case ASCII_NUMERIC:
414                    case ASCII_ALPHA:
415                    case ASCII_ALPHA_NUMERIC:
416                    case ASCII_ALPHA_NUMERIC_SPACE:
417                    case ASCII_ALPHA_NUMERIC_SPECIAL:
418                        packedValue = new byte[asciiInterpreter.getPackedLength(tagValue.length())];
419                        asciiInterpreter.interpret(tagValue, packedValue, 0);
420                        break;
421                    case BINARY:
422                    case PROPRIETARY:
423                        packedValue = new byte[literalInterpreter.getPackedLength(tagValue.length())];
424                        literalInterpreter.interpret(tagValue, packedValue, 0);
425                        break;
426                    case CONSTRUCTED:
427                        throw new IllegalArgumentException("CONSTRUCTED tag value should be a composite ISOComponent");
428                        //packedValue = new byte[literalInterpreter.getPackedLength(tagValue.length())];
429                        //literalInterpreter.interpret(tagValue, packedValue, 0);
430                        //break;
431                    default:
432                        throw new IllegalArgumentException("Unknown TLVDataFormat: " + dataFormat);
433                }
434            } else {
435                packedValue = c.getBytes();
436            }
437        } else {
438            if (TLVDataFormat.CONSTRUCTED.equals(dataFormat) || TLVDataFormat.PROPRIETARY.equals(dataFormat)) {
439                packedValue = pack(c, true, 0, c.getMaxField());
440            } else {
441                throw new IllegalArgumentException("Composite ISOComponent should be used only for CONSTRUCTED data type");
442            }
443        }
444        return packedValue;
445    }
446
447    private ISOComponent unpackValue(String tagNameHex, final byte[] tlvData,
448                                     int subFieldNumber, int dataLength) throws ISOException, UnknownTagNumberException {
449        final int tagNumber = Integer.parseInt(tagNameHex, 16);
450        final TLVDataFormat dataFormat = getTagFormatMapper().getFormat(tagNumber);
451        ISOComponent value;
452        String unpackedValue;
453        int uninterpretLength;
454        switch (dataFormat) {
455            case COMPRESSED_NUMERIC:
456                uninterpretLength = getUninterpretLength(dataLength, bcdInterpreterRightPaddedF);
457                unpackedValue = bcdInterpreterRightPaddedF.uninterpret(tlvData, 0, uninterpretLength);
458                value = new ISOField(subFieldNumber, ISOUtil.unPadRight(unpackedValue, 'F'));
459                break;
460            case PACKED_NUMERIC:
461                uninterpretLength = getUninterpretLength(dataLength, bcdInterpreterLeftPaddedZero);
462                unpackedValue = bcdInterpreterLeftPaddedZero.uninterpret(tlvData, 0, uninterpretLength);
463                value = new ISOField(subFieldNumber, ISOUtil.unPadLeft(unpackedValue, '0'));
464                break;
465            case PACKED_NUMERIC_DATE_YYMMDD:
466            case PACKED_NUMERIC_TIME_HHMMSS:
467                uninterpretLength = getUninterpretLength(dataLength, bcdInterpreterLeftPaddedZero);
468                unpackedValue = bcdInterpreterLeftPaddedZero.uninterpret(tlvData, 0, uninterpretLength);
469                value = new ISOField(subFieldNumber, unpackedValue);
470                break;
471            case ASCII_NUMERIC:
472            case ASCII_ALPHA:
473            case ASCII_ALPHA_NUMERIC:
474            case ASCII_ALPHA_NUMERIC_SPACE:
475            case ASCII_ALPHA_NUMERIC_SPECIAL:
476                uninterpretLength = getUninterpretLength(dataLength, asciiInterpreter);
477                unpackedValue = asciiInterpreter.uninterpret(tlvData, 0, uninterpretLength);
478                value = new ISOField(subFieldNumber, unpackedValue);
479                break;
480            case BINARY:
481            case PROPRIETARY:
482                value = new ISOBinaryField(subFieldNumber, tlvData);
483                break;
484            case CONSTRUCTED:
485                value = new ISOMsg(subFieldNumber);
486                unpack(value, tlvData, true);
487                break;
488            default:
489                throw new IllegalArgumentException("Unknown TLVDataFormat: " + dataFormat);
490        }
491        return value;
492    }
493
494    @Override
495    public void unpack(ISOComponent m, InputStream in) throws IOException, ISOException {
496        throw new IllegalStateException(
497                "Call to unpack(ISOComponent m, InputStream in) was not expected.");
498    }
499
500    private int getUninterpretLength(int length, BinaryInterpreter interpreter) {
501        if (length > 0) {
502            int lengthAdjusted = length + length % 2;
503            return (length * lengthAdjusted) / interpreter.getPackedLength(lengthAdjusted);
504        }
505        return 0;
506    }
507
508    private int getUninterpretLength(int length, Interpreter interpreter) {
509        if (length > 0) {
510            int lengthAdjusted = length + length % 2;
511            return (length * lengthAdjusted) / interpreter.getPackedLength(lengthAdjusted);
512        }
513        return 0;
514    }
515
516    private class UnpackResult {
517
518        private final byte[] value;
519        private final int consumed;
520
521        private UnpackResult(final byte[] value, final int consumed) {
522            this.value = value;
523            this.consumed = consumed;
524        }
525    }
526
527}