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}