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; 020 021import java.io.PrintStream; 022import java.math.BigInteger; 023import java.util.Arrays; 024import org.jpos.iso.ISOUtil; 025import org.jpos.util.Loggeable; 026 027/** 028 * A single TLV (Tag-Length-Value) message element. 029 * @author bharavi 030 */ 031public class TLVMsg implements Loggeable { 032 033 /** 034 * Value not used as tag id in accordance with ISO/IEC 7816. 035 */ 036 private static final int SKIP_BYTE1 = 0x00; 037 038 /** 039 * Value not used as tag id in accordance with ISO/IEC 7816. 040 */ 041 private static final int SKIP_BYTE2 = 0xFF; 042 043 private static final int EXT_TAG_MASK = 0x1F; 044 045 /** 046 * Enforces fixed tag size. 047 * <p> 048 * Zero means that the tag size will be determined in accordance with 049 * ISO/IEC 7816. 050 */ 051 private final int tagSize; 052 053 /** 054 * Enforces fixed length size. 055 * <p> 056 * Zero means that the length size will be determined in accordance with 057 * ISO/IEC 7816. 058 */ 059 private final int lengthSize; 060 061 private final int tag; 062 private final byte[] value; 063 064 /** 065 * Constructs a TLV message from tag and value. 066 * 067 * @param tag id 068 * @param value tag value 069 * @deprecated In most cases, a message is created to attach it to the list. 070 * <br> 071 * It can be done by: 072 * <pre>{@code 073 * TLVList tl = ...; 074 * tl.append(tag, value); 075 * }</pre> 076 * If for some reason this is not possible then a message can be created: 077 * <pre>{@code 078 * TLVList tl = TLVListBuilder.createInstance().build(); // or just new TLVList(); 079 * tl.append(tag, value); 080 * TLVMsg tm = tl.find(tag); 081 * }</pre> 082 * The intention is to not promote the use of TLVMsg outside. Due to 083 * the lack of compatibility of various TLV types at TLVList.append(TLVMsg) 084 */ 085 @Deprecated 086 public TLVMsg(int tag, byte[] value) throws IllegalArgumentException { 087 this(tag, value, 0, 0); 088 } 089 090 /** 091 * Constructs a TLV message using explicit tag and length sizes. 092 * 093 * @param tag the TLV tag identifier 094 * @param value the TLV value bytes 095 * @param tagSize fixed tag size, or {@code 0} for auto-detection 096 * @param lengthSize fixed length size, or {@code 0} for auto-detection 097 * @throws IllegalArgumentException if the supplied tag or value is invalid 098 */ 099 protected TLVMsg(int tag, byte[] value, int tagSize, int lengthSize) 100 throws IllegalArgumentException { 101 this.tag = tag; 102 this.value = value; 103 this.tagSize = tagSize; 104 this.lengthSize = lengthSize; 105 106 if (tagSize == 0) 107 verifyTag(tag); 108 else 109 verifyTagLength(tag); 110 111 if (lengthSize > 0) 112 verifyValue(value); 113 114 } 115 116 private boolean isExtTagByte(int b) { 117 return (b & EXT_TAG_MASK) == EXT_TAG_MASK; 118 } 119 120 /** 121 * Verify tag identifier. 122 * <p> 123 * Tag number in accordance with ISO/IEC 7816 124 * <ol> 125 * <li>The tag field consists of one or more consecutive bytes. It is a number. 126 * <li>ISO/IEC 7816 uses neither ’00’ nor ‘FF’ as tag value. 127 * <li>If the bits B5-B1 of the leading byte are not all set to 1, then 128 * may they shall encode an integer equal to the tag number. Then the 129 * tag field consists of a single byte. 130 * Otherwise <i>(B5-B1 set to 1 in the leading byte)</i>, the tag 131 * field shall continue on one or more subsequent bytes. 132 * </li> 133 * </ol> 134 * 135 * See <a href="http://cardwerk.com/iso7816-4-annex-d-use-of-basic-encoding-rules-asn-1/#AnnexD_2">ISO 7816-4 Annex D.2: Tag field</a> 136 * 137 * @param tag tag identifier 138 * @throws IllegalArgumentException if tag identifier is zero or less or 139 * it is included in the illegal ranges. 140 */ 141 private void verifyTag(int tag) throws IllegalArgumentException { 142 if (tag <= 0) 143 throw new IllegalArgumentException("Tag id must be greater than zero"); 144 145 BigInteger bi = BigInteger.valueOf(tag); 146 byte[] ba = bi.toByteArray(); 147 148 if (ba[0] == 0x00) { 149 // strip byte array if starts with 0x00 150 ba = Arrays.copyOfRange(ba, 1, ba.length); 151 } 152 153 int idx = 0; 154 do { 155 boolean byteFollows = (idx == 0 && isExtTagByte(ba[idx])) || (idx > 0 && (ba[idx] & 0x80) == 0x80); 156 if ((ba[idx] & 0xff) == SKIP_BYTE1) { 157 throw new IllegalArgumentException("Tag id: 0x" + Integer.toString(tag, 0x10).toUpperCase() 158 + " cannot contain in any 0x00 byte" 159 ); 160 } else if ((ba[idx] & 0xff) == SKIP_BYTE2) { 161 throw new IllegalArgumentException("Tag id: 0x" + Integer.toString(tag, 0x10).toUpperCase() 162 + " cannot contain in any 0xff byte" 163 ); 164 } else if (byteFollows && ba.length <= idx + 1) { 165 throw new IllegalArgumentException("Tag id: 0x" + Integer.toString(tag, 0x10).toUpperCase() 166 + " shall contain subsequent byte" 167 ); 168 } else if (!byteFollows && idx+1 < ba.length) { 169 throw new IllegalArgumentException("Tag id: 0x" + Integer.toString(tag, 0x10).toUpperCase() 170 + " cannot contain subsequent byte" 171 ); 172 } 173 idx++; 174 } while (idx < ba.length); 175 } 176 177 private void verifyTagLength(int tag) throws IllegalArgumentException { 178 if (tag < 0) 179 throw new IllegalArgumentException("The tag id must be greater than or equals zero"); 180 181 int maxTag = 1 << (tagSize << 3); 182 maxTag -= 1; 183 if (tag > maxTag) 184 throw new IllegalArgumentException("The tag id cannot be greater that: " + maxTag); 185 } 186 187 private void verifyValue(byte[] value) throws IllegalArgumentException { 188 if (value == null) 189 return; 190 191 int maxLength = 1 << (lengthSize << 3); 192 maxLength -= 1; 193 if (value.length > maxLength) 194 throw new IllegalArgumentException("The tag value length cannot exceed: " + maxLength); 195 } 196 197 /** 198 * Returns the TLV tag identifier. 199 * 200 * @return tag 201 */ 202 public int getTag() { 203 return tag; 204 } 205 206 /** 207 * Returns the TLV value bytes. 208 * 209 * @return tag value 210 */ 211 public byte[] getValue() { 212 return value; 213 } 214 215 /** 216 * Returns the encoded TLV message bytes. 217 * 218 * @return tag + length + value of the TLV message 219 */ 220 public byte[] getTLV() { 221 String hexTag = Integer.toHexString(tag); 222 byte[] bTag = ISOUtil.hex2byte(hexTag); 223 if (tagSize > 0) 224 bTag = fitInArray(bTag, tagSize); 225 226 byte[] bLen = getL(); 227 byte[] bVal = getValue(); 228 if (bVal == null) 229 //Value can be null 230 bVal = new byte[0]; 231 232 int tLength = bTag.length + bLen.length + bVal.length; 233 byte[] out = new byte[tLength]; 234 System.arraycopy(bTag, 0, out, 0, bTag.length); 235 System.arraycopy(bLen, 0, out, bTag.length, bLen.length); 236 System.arraycopy(bVal, 0, out, bTag.length + bLen.length, 237 bVal.length 238 ); 239 return out; 240 } 241 242 private byte[] fitInArray(byte[] bytes, int length) { 243 byte[] ret = new byte[length]; 244 if (bytes.length <= length) 245 // copy bytes at end of ret 246 System.arraycopy(bytes, 0, ret, ret.length - bytes.length, bytes.length); 247 else 248 // copy last tagSize bytes of bytes 249 System.arraycopy(bytes, bytes.length - ret.length, ret, 0, ret.length); 250 return ret; 251 } 252 253 private byte[] getLengthArray() { 254 int length = 0; 255 if (value != null) 256 length = value.length; 257 258 byte[] ret = BigInteger.valueOf(length).toByteArray(); 259 return fitInArray(ret, lengthSize); 260 } 261 262 /** 263 * Value up to 127 can be encoded in single byte and multiple bytes are 264 * required for length bigger than 127 265 * 266 * @return encoded length 267 */ 268 public byte[] getL() { 269 270 if (lengthSize > 0) 271 return getLengthArray(); 272 273 if (value == null) 274 return new byte[1]; 275 276 // if Length is less than 128 277 // set the 8bit as 0 indicating next 7 bits is the length 278 // of the message 279 // if length is more than 127 then, set the first bit as 1 indicating 280 // next 7 bits will indicate the length of following bytes used for 281 // length 282 283 BigInteger bi = BigInteger.valueOf(value.length); 284 /* Value to be encoded on multiple bytes */ 285 byte[] rBytes = bi.toByteArray(); 286 /* If value can be encoded on one byte */ 287 if (value.length < 0x80) 288 return rBytes; 289 290 //we need 1 byte to indicate the length 291 //for that is used sign byte (first 8-bits equals 0), 292 //if it is not present it is added 293 if (rBytes[0] > 0) 294 rBytes = ISOUtil.concat(new byte[1], rBytes); 295 rBytes[0] = (byte) (0x80 | rBytes.length - 1); 296 297 return rBytes; 298 } 299 300 /** 301 * Returns the TLV value as a hexadecimal string. 302 * 303 * @return value 304 */ 305 public String getStringValue() { 306 return ISOUtil.hexString(value); 307 } 308 309 @Override 310 public String toString(){ 311 String t = Integer.toHexString(tag); 312 if (t.length() % 2 > 0) 313 t = "0" + t; 314 return String.format("[tag: 0x%s, %s]", t 315 ,value == null ? null : getStringValue() 316 ); 317 } 318 319 @Override 320 public void dump(PrintStream p, String indent) { 321 p.print(indent); 322 p.print("<tag id='"); 323 p.print(Integer.toHexString(getTag())); 324 p.print("' value='"); 325 p.print(ISOUtil.hexString(getValue())); 326 p.println("' />"); 327 } 328 329 private boolean isPrimitive (byte firstByte) { 330 return (firstByte & 0x20) == 0x00; 331 } 332 333}