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}