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    public BERTLVPackager() throws ISOException {
070        super();
071        tagInterpreter = getTagInterpreter();
072        lengthInterpreter = getLengthInterpreter();
073        valueInterpreter = getValueInterpreter();
074    }
075
076    protected abstract BinaryInterpreter getTagInterpreter();
077
078    protected abstract BinaryInterpreter getLengthInterpreter();
079
080    protected abstract BinaryInterpreter getValueInterpreter();
081
082    protected abstract BERTLVFormatMapper getTagFormatMapper();
083
084    /**
085     * Pack the sub-field into a byte array
086     */
087    @Override
088    public byte[] pack(ISOComponent m) throws ISOException {
089        return pack(m, false, getFirstField(), m.getMaxField());
090    }
091
092    public byte[] pack(ISOComponent m, boolean nested, int startIdx, int endIdx)
093            throws ISOException {
094        LogEvent evt = new LogEvent(this, "pack");
095        try (ByteArrayOutputStream bout = new ByteArrayOutputStream(100)) {
096            ISOComponent c;
097            Map fields = m.getChildren();
098            for (int i = startIdx; i <= endIdx; i++) {
099                c = (ISOComponent) fields.get(i);
100                if (c != null) {
101                    try {
102                        final byte[] b;
103                        if (c instanceof ISOTaggedField) {
104                            b = packTLV((ISOTaggedField) c);
105                        } else {
106
107                            if (c.getValue() == null) {
108                                b = new byte[0];
109                            } else if (!nested && (i == startIdx || i == endIdx) &&
110                                    this.fld.length > i && this.fld[i] != null) {
111                                b = this.fld[i].pack(c);
112                            } else {
113                                throw new ISOException(
114                                        "Field: " +
115                                                i +
116                                                " of type: " +
117                                                c.getClass() +
118                                                " cannot be packed. Either the object should be of type ISOTagField" +
119                                                " OR this should be the first or last sub-field and a packager" +
120                                                " should be configured for the same");
121                            }
122                        }
123                        bout.write(b);
124                    } catch (Exception e) {
125                        evt.addMessage("error packing sub-field " + i);
126                        evt.addMessage(c);
127                        evt.addMessage(e);
128                        throw e;
129                    }
130                }
131            }
132
133            byte[] d = bout.toByteArray();
134            if (logger != null) // save a few CPU cycle if no logger available
135                evt.addMessage(ISOUtil.hexString(d));
136            return d;
137        } catch (ISOException e) {
138            evt.addMessage(e);
139            throw e;
140        } catch (Exception e) {
141            evt.addMessage(e);
142            throw new ISOException(e);
143        } finally {
144            Logger.log(evt);
145        }
146    }
147
148    private byte[] packTLV(ISOTaggedField c) throws ISOException {
149        byte[] b;
150        final byte[] rawValueBytes;
151
152        try {
153            rawValueBytes = packValue(c.getTag(), c);
154        } catch (UnknownTagNumberException e) {
155            throw new ISOException(e);
156        }
157
158        byte[] valueBytes = new byte[valueInterpreter.getPackedLength(rawValueBytes.length)];
159        valueInterpreter.interpret(rawValueBytes, valueBytes, 0);
160
161        byte[] tagBytes = packTag(c);
162        byte[] lengthBytes = packLength(valueBytes);
163
164        b = new byte[tagBytes.length + lengthBytes.length + valueBytes.length];
165        System.arraycopy(tagBytes, 0, b, 0, tagBytes.length);
166        System.arraycopy(lengthBytes, 0, b, tagBytes.length, lengthBytes.length);
167        System.arraycopy(valueBytes, 0, b, tagBytes.length + lengthBytes.length, valueBytes.length);
168        return b;
169    }
170
171    private byte[] packTag(final ISOTaggedField c) {
172        final byte[] tagBytes;
173        String tag = c.getTag();
174        tagBytes = ISOUtil.hex2byte(tag);
175        byte[] packedTagBytes = new byte[tagInterpreter.getPackedLength(tagBytes.length)];
176        tagInterpreter.interpret(tagBytes, packedTagBytes, 0);
177        return packedTagBytes;
178    }
179
180    private byte[] packLength(final byte[] valueBytes) {
181        final byte[] lengthBytes;
182        int length = valueBytes.length;
183        if (length > 0x7F) {
184            byte[] lengthBytesSuffix = ISOUtil.int2byte(length);
185            lengthBytes = new byte[lengthBytesSuffix.length + 1];
186            lengthBytes[0] = (byte) (0x80 | lengthBytesSuffix.length);
187            System.arraycopy(lengthBytesSuffix, 0, lengthBytes, 1, lengthBytesSuffix.length);
188        } else {
189            lengthBytes = new byte[]{(byte) length};
190        }
191        byte[] packedLengthBytes = new byte[lengthInterpreter.getPackedLength(lengthBytes.length)];
192        lengthInterpreter.interpret(lengthBytes, packedLengthBytes, 0);
193        return packedLengthBytes;
194
195    }
196
197    @Override
198    public int unpack(ISOComponent m, byte[] b) throws ISOException {
199        try {
200            return unpack(m, b, false);
201        } catch (RuntimeException e) {
202            throw new ISOException(e);
203        }
204    }
205
206    public int unpack(ISOComponent m, byte[] b, boolean nested) throws ISOException {
207        LogEvent evt = new LogEvent(this, "unpack");
208        try {
209            if (m.getComposite() == null)
210                throw new ISOException("Can't call packager on non Composite");
211            if (b.length == 0)
212                return 0; // nothing to do
213            if (logger != null) // save a few CPU cycle if no logger available
214                evt.addMessage(ISOUtil.hexString(b));
215
216            int tlvDataLength = b.length;
217
218            int consumed = 0;
219            int subFieldNumber = 1;
220            if (!nested && fld.length > 1) {
221                ISOFieldPackager packager = fld[1];
222                if (packager != null) {
223                    ISOComponent subField = packager.createComponent(1);
224                    consumed = consumed + packager.unpack(subField, b, consumed);
225                    m.set(subField);
226                }
227                subFieldNumber++;
228            }
229
230            while (consumed < tlvDataLength) {
231                ISOFieldPackager packager;
232                if (!nested && fld.length > 1 && (packager = fld[fld.length - 1]) != null &&
233                        packager.getLength() == tlvDataLength - consumed) {
234                    ISOComponent subField = packager.createComponent(fld.length - 1);
235                    consumed = consumed + packager.unpack(subField, b, consumed);
236                    m.set(subField);
237                    subFieldNumber++;
238                } else {
239                    //Read the Tag per BER
240                    UnpackResult tagUnpackResult = unpackTag(b, consumed);
241                    consumed = consumed + tagUnpackResult.consumed;
242                    final byte[] tagBytes = tagUnpackResult.value;
243                    String tag = ISOUtil.byte2hex(tagBytes).toUpperCase();
244                    UnpackResult lengthUnpackResult = unpackLength(b, consumed);
245                    consumed = consumed + lengthUnpackResult.consumed;
246                    int length = ISOUtil.byte2int(lengthUnpackResult.value);
247
248                    final ISOComponent tlvSubFieldData;
249                    byte[] value = new byte[length];
250
251                    if (length > 0) {
252                        System.arraycopy(b, consumed, value, 0, value.length);
253                    }
254
255                    int uninterpretLength = getUninterpretLength(length, valueInterpreter);
256                    byte[] rawValueBytes =
257                            valueInterpreter.uninterpret(value, 0, uninterpretLength);
258
259                    tlvSubFieldData = unpackValue(tag, rawValueBytes, subFieldNumber, length);
260                    consumed = consumed + length;
261                    ISOTaggedField tlv = new ISOTaggedField(tag, tlvSubFieldData);
262                    m.set(tlv);
263                    subFieldNumber++;
264                }
265            }
266            if (b.length != consumed) {
267                evt.addMessage("WARNING: unpack len=" + b.length + " consumed=" + consumed);
268            }
269            return consumed;
270        } catch (ISOException e) {
271            evt.addMessage(e);
272            throw e;
273        } catch (Exception e) {
274            evt.addMessage(e);
275            throw new ISOException(e);
276        } finally {
277            Logger.log(evt);
278        }
279    }
280
281    private UnpackResult unpackTag(final byte[] tlvData, final int offset) {
282        byte[] tlvBytesHex =
283                tagInterpreter.uninterpret(
284                        tlvData,
285                        offset,
286                        tlvData.length >= offset + MAX_TAG_BYTES
287                                ? MAX_TAG_BYTES : tlvData.length - offset);
288        int index = 0;
289        final byte[] tagBytes;
290        byte tagByte = tlvBytesHex[index];
291        int tagLength = 1;
292        if ((tagByte & 0x1F) == 0x1F) {
293            tagLength++;
294            tagByte = tlvBytesHex[index + 1];
295            while (/* tagLength < MAX_TAG_BYTES && */(tagByte & 0x80) == 0x80) {
296                tagLength++;
297                tagByte = tlvBytesHex[index + tagLength - 1];
298            }
299            tagBytes = new byte[tagLength];
300            System.arraycopy(tlvBytesHex, index, tagBytes, 0, tagBytes.length);
301        } else {
302            tagBytes = new byte[]{tagByte};
303        }
304        return new UnpackResult(tagBytes, tagInterpreter.getPackedLength(tagLength));
305    }
306
307    private UnpackResult unpackLength(final byte[] tlvData, final int offset) {
308        byte[] tlvBytesHex =
309                lengthInterpreter.uninterpret(
310                        tlvData,
311                        offset,
312                        tlvData.length >= offset + MAX_LENGTH_BYTES
313                                ? MAX_LENGTH_BYTES : tlvData.length - offset);
314        final byte length = tlvBytesHex[0];
315        final int lengthLength;
316        final byte[] lengthBytes;
317        if ((length & 0x80) == 0x80) {
318            //Long Form
319            int lengthOctetsCount = length & 0x7F;
320            lengthLength = lengthOctetsCount + 1;
321            lengthBytes = new byte[lengthOctetsCount];
322            System.arraycopy(tlvBytesHex, 1, lengthBytes, 0, lengthOctetsCount);
323        } else {
324            //Short Form
325            lengthLength = 1;
326            lengthBytes = new byte[]{length};
327        }
328        return new UnpackResult(lengthBytes, lengthInterpreter.getPackedLength(lengthLength));
329    }
330
331    protected byte[] packValue(String tagNameHex, final ISOComponent c) throws ISOException,
332            UnknownTagNumberException {
333        final int tagNumber = Integer.parseInt(tagNameHex, 16);
334        final TLVDataFormat dataFormat = getTagFormatMapper().getFormat(tagNumber);
335        String tagValue;
336        byte[] packedValue;
337
338        if (c.getComposite() == null) {
339            if (c.getValue() instanceof String) {
340                tagValue = (String) c.getValue();
341                EMVStandardTagType tagType;
342                int length;
343                if (EMVStandardTagType.isProprietaryTag(Integer.parseInt(tagNameHex, 16))) {
344                    length = tagValue.length();
345                } else {
346                    tagType = EMVStandardTagType.forHexCode(tagNameHex);
347                    length = Math.max(tagValue.length(), tagType.getDataLength().getMinLength());
348                }
349                switch (dataFormat) {
350                    case COMPRESSED_NUMERIC:
351                        packedValue = new byte[bcdInterpreterRightPaddedF.getPackedLength(length)];
352                        bcdInterpreterRightPaddedF.interpret(tagValue, packedValue, 0);
353                        break;
354                    case PACKED_NUMERIC:
355                    case PACKED_NUMERIC_DATE_YYMMDD:
356                    case PACKED_NUMERIC_TIME_HHMMSS:
357                        packedValue = new byte[bcdInterpreterLeftPaddedZero.getPackedLength(length)];
358                        bcdInterpreterLeftPaddedZero.interpret(ISOUtil.zeropad(tagValue, length), packedValue, 0);
359                        break;
360                    case ASCII_NUMERIC:
361                    case ASCII_ALPHA:
362                    case ASCII_ALPHA_NUMERIC:
363                    case ASCII_ALPHA_NUMERIC_SPACE:
364                    case ASCII_ALPHA_NUMERIC_SPECIAL:
365                        packedValue = new byte[asciiInterpreter.getPackedLength(tagValue.length())];
366                        asciiInterpreter.interpret(tagValue, packedValue, 0);
367                        break;
368                    case BINARY:
369                    case PROPRIETARY:
370                        packedValue = new byte[literalInterpreter.getPackedLength(tagValue.length())];
371                        literalInterpreter.interpret(tagValue, packedValue, 0);
372                        break;
373                    case CONSTRUCTED:
374                        throw new IllegalArgumentException("CONSTRUCTED tag value should be a composite ISOComponent");
375                        //packedValue = new byte[literalInterpreter.getPackedLength(tagValue.length())];
376                        //literalInterpreter.interpret(tagValue, packedValue, 0);
377                        //break;
378                    default:
379                        throw new IllegalArgumentException("Unknown TLVDataFormat: " + dataFormat);
380                }
381            } else {
382                packedValue = c.getBytes();
383            }
384        } else {
385            if (TLVDataFormat.CONSTRUCTED.equals(dataFormat) || TLVDataFormat.PROPRIETARY.equals(dataFormat)) {
386                packedValue = pack(c, true, 0, c.getMaxField());
387            } else {
388                throw new IllegalArgumentException("Composite ISOComponent should be used only for CONSTRUCTED data type");
389            }
390        }
391        return packedValue;
392    }
393
394    private ISOComponent unpackValue(String tagNameHex, final byte[] tlvData,
395                                     int subFieldNumber, int dataLength) throws ISOException, UnknownTagNumberException {
396        final int tagNumber = Integer.parseInt(tagNameHex, 16);
397        final TLVDataFormat dataFormat = getTagFormatMapper().getFormat(tagNumber);
398        ISOComponent value;
399        String unpackedValue;
400        int uninterpretLength;
401        switch (dataFormat) {
402            case COMPRESSED_NUMERIC:
403                uninterpretLength = getUninterpretLength(dataLength, bcdInterpreterRightPaddedF);
404                unpackedValue = bcdInterpreterRightPaddedF.uninterpret(tlvData, 0, uninterpretLength);
405                value = new ISOField(subFieldNumber, ISOUtil.unPadRight(unpackedValue, 'F'));
406                break;
407            case PACKED_NUMERIC:
408                uninterpretLength = getUninterpretLength(dataLength, bcdInterpreterLeftPaddedZero);
409                unpackedValue = bcdInterpreterLeftPaddedZero.uninterpret(tlvData, 0, uninterpretLength);
410                value = new ISOField(subFieldNumber, ISOUtil.unPadLeft(unpackedValue, '0'));
411                break;
412            case PACKED_NUMERIC_DATE_YYMMDD:
413            case PACKED_NUMERIC_TIME_HHMMSS:
414                uninterpretLength = getUninterpretLength(dataLength, bcdInterpreterLeftPaddedZero);
415                unpackedValue = bcdInterpreterLeftPaddedZero.uninterpret(tlvData, 0, uninterpretLength);
416                value = new ISOField(subFieldNumber, unpackedValue);
417                break;
418            case ASCII_NUMERIC:
419            case ASCII_ALPHA:
420            case ASCII_ALPHA_NUMERIC:
421            case ASCII_ALPHA_NUMERIC_SPACE:
422            case ASCII_ALPHA_NUMERIC_SPECIAL:
423                uninterpretLength = getUninterpretLength(dataLength, asciiInterpreter);
424                unpackedValue = asciiInterpreter.uninterpret(tlvData, 0, uninterpretLength);
425                value = new ISOField(subFieldNumber, unpackedValue);
426                break;
427            case BINARY:
428            case PROPRIETARY:
429                value = new ISOBinaryField(subFieldNumber, tlvData);
430                break;
431            case CONSTRUCTED:
432                value = new ISOMsg(subFieldNumber);
433                unpack(value, tlvData, true);
434                break;
435            default:
436                throw new IllegalArgumentException("Unknown TLVDataFormat: " + dataFormat);
437        }
438        return value;
439    }
440
441    @Override
442    public void unpack(ISOComponent m, InputStream in) throws IOException, ISOException {
443        throw new IllegalStateException(
444                "Call to unpack(ISOComponent m, InputStream in) was not expected.");
445    }
446
447    private int getUninterpretLength(int length, BinaryInterpreter interpreter) {
448        if (length > 0) {
449            int lengthAdjusted = length + length % 2;
450            return (length * lengthAdjusted) / interpreter.getPackedLength(lengthAdjusted);
451        }
452        return 0;
453    }
454
455    private int getUninterpretLength(int length, Interpreter interpreter) {
456        if (length > 0) {
457            int lengthAdjusted = length + length % 2;
458            return (length * lengthAdjusted) / interpreter.getPackedLength(lengthAdjusted);
459        }
460        return 0;
461    }
462
463    private class UnpackResult {
464
465        private final byte[] value;
466        private final int consumed;
467
468        private UnpackResult(final byte[] value, final int consumed) {
469            this.value = value;
470            this.consumed = consumed;
471        }
472    }
473
474}