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