TLV(tag-length-value),即每个子域由tag标签(T),子域取值的长度(L)和子域取值(V)构成。
tag标签的属性为bit,由16进制表示,占1~2个字节长度。例如,“9F33”为一个占用两个字节的tag标签。而“95”为一个占用一个字节的tag标签。若tag标签的第一个字节(注:字节排序方向为从左往右数,第一个字节即为最左边的字节。bit排序规则同理。)的后四个bit为“1111”,则说明该tag占两个字节,例如“9F33”;否则占一个字节,例如“95”。
子域长度(即L本身)的属性也为bit,占1~3个字节长度。具体编码规则如下:
a) 当L字段最左边字节的最左bit位(即bit8)为0,表示该L字段占一个字节,它的后续7个bit位(即bit7~bit1)表示子域取值的长度,采用二进制数表示子域取值长度的十进制数。例如,某个域取值占3个字节,那么其子域取值长度表示为“00000011”。所以,若子域取值的长度在1~127字节之间,那么该L字段本身仅占一个字节。
b) 当L字段最左边字节的最左bit位(即bit8)为1,表示该L字段不止占一个字节,那么它到底占几个字节由该最左字节的后续7个bit位(即bit7~bit1)的十进制取值表示。例如,若最左字节为10000010,表示L字段除该字节外,后面还有两个字节。其后续字节的十进制取值表示子域取值的长度。例如,若L字段为“1000 0001 1111 1111”,表示该子域取值占255个字节。所以,若子域取值的长度在127~255字节之间,那么该L字段本身需占两个字节。
tag标签的属性为bit,由16进制表示,占1~2个字节长度。例如,“9F33”为一个占用两个字节的tag标签。而“95”为一个占用一个字节的tag标签。若tag标签的第一个字节(注:字节排序方向为从左往右数,第一个字节即为最左边的字节。bit排序规则同理。)的后四个bit为“1111”,则说明该tag占两个字节,例如“9F33”;否则占一个字节,例如“95”。
子域长度(即L本身)的属性也为bit,占1~3个字节长度。具体编码规则如下:
a) 当L字段最左边字节的最左bit位(即bit8)为0,表示该L字段占一个字节,它的后续7个bit位(即bit7~bit1)表示子域取值的长度,采用二进制数表示子域取值长度的十进制数。例如,某个域取值占3个字节,那么其子域取值长度表示为“00000011”。所以,若子域取值的长度在1~127字节之间,那么该L字段本身仅占一个字节。
b) 当L字段最左边字节的最左bit位(即bit8)为1,表示该L字段不止占一个字节,那么它到底占几个字节由该最左字节的后续7个bit位(即bit7~bit1)的十进制取值表示。例如,若最左字节为10000010,表示L字段除该字节外,后面还有两个字节。其后续字节的十进制取值表示子域取值的长度。例如,若L字段为“1000 0001 1111 1111”,表示该子域取值占255个字节。所以,若子域取值的长度在127~255字节之间,那么该L字段本身需占两个字节。
public class TLVUtil {
/**
* @Title:encodeTLV
* @Description: 编码键值对为tlv格式字符串
* @param map 键值对,键为16进制字符串tag,值为16进制字符串。若原字符串为ascii字符串,需先转为16进制字符串
* @return String tlv字符串
*/
public static String encodeTLV(Map<String, String> map) {
if (map == null) {
return null;
}
StringBuilder sb = new StringBuilder();
for (String key : map.keySet()) {
if (key == null) {
continue;
}
String tag = key;
if (key.length() % 2 != 0) {
key = StringUtils.leftPad(key, key.length() + 1, '0');
}
if (!Pattern.compile("[0-9a-fA-F]([0-9a-eA-E]|(F[0-9a-fA-F]{2}))").matcher(key).matches()) {
throw new RuntimeException("tag[" + key + "]格式错误");
}
sb.append(tag);
String hexV = map.get(key);
if (hexV == null) {
sb.append("00");
continue;
}
byte[] vbytes = DatatypeConverter.parseHexBinary(hexV);
if (vbytes.length > 0xFFFF) {
throw new RuntimeException("value长度不能超过65535");
} else if (vbytes.length > 0x7F) {
String lstr = Integer.toHexString(vbytes.length);
if ((lstr.length() % 2) != 0) {
lstr = StringUtils.leftPad(lstr, lstr.length() + 1, '0');
}
String hlstr = Integer.toHexString(0x80 | (lstr.length() / 2));
if ((hlstr.length() % 2) != 0) {
hlstr = StringUtils.leftPad(hlstr, lstr.length() + 1, '0');
}
sb.append(hlstr).append(lstr);
} else {
String lstr = Integer.toHexString(vbytes.length);
if ((lstr.length() % 2) != 0) {
lstr = StringUtils.leftPad(lstr, lstr.length() + 1, '0');
}
sb.append(lstr);
}
sb.append(DatatypeConverter.printHexBinary(vbytes));
}
return sb.toString();
}
/**
* @Title:encodeTLV2
* @Description: 编码键值对为tlv格式字符串
* @param map 两级键值对,子父键都为16进制字符串,子级值为ascii编码
* @return String tlv字符串
*/
public static String encodeTLV2(Map<String, Map<String, String>> map) {
if (map == null) {
return null;
}
Map<String, String> tpmap = new HashMap<String, String>();
for (String key : map.keySet()) {
Map<String, String> tmap = map.get(key);
Map<String, String> tcmap = new HashMap<String, String>();
for (String ckey : tmap.keySet()) {
if(tmap.get(ckey) == null) {
continue;
}
tcmap.put(ckey, DatatypeConverter.printHexBinary(tmap.get(ckey).getBytes()));
}
tpmap.put(key, encodeTLV(tcmap));
}
return encodeTLV(tpmap);
}
/**
* @Title:decodeTLV
* @Description: 解码 TLV 16进制字符串
* @param tlvStr 16进制字符串
* @return Map<String, String> 键为tag,值为tag对应V的16进制字符串。注:V通常需要再处理(如:截取、再解码或16进制转utf8字符串等等)。
*/
public static Map<String, String> decodeTLV(String tlvStr) {
Map<String, String> map = new LinkedHashMap<String,String>();
if (StringUtils.isBlank(tlvStr)) {
return map;
}
CharBuffer charBuffer = CharBuffer.wrap(tlvStr.toCharArray());
charBuffer.mark();
char[] tagChars = new char[2];
charBuffer.get(tagChars);
if ((Integer.valueOf(String.valueOf(tagChars), 16) & 0x0F) == 0x0F) {
charBuffer.reset();
tagChars = new char[4];
charBuffer.get(tagChars);
}
char[] lenChars = new char[2];
charBuffer.get(lenChars);
int len = Integer.parseInt(String.valueOf(lenChars), 16);
if (len > 0x7F) {
lenChars = new char[len & 0x7F];
charBuffer.get(lenChars);
len = Integer.parseInt(String.valueOf(lenChars));
}
char[] valChars = new char[len * 2];
charBuffer.get(valChars);
map.put(String.valueOf(tagChars), String.valueOf(valChars));
if (charBuffer.remaining() > 0) {
char[] remainingChars = new char[charBuffer.remaining()];
charBuffer.get(remainingChars);
map.putAll(decodeTLV(String.valueOf(remainingChars)));
}
return map;
}
/**
* @Title:decodeTLV2
* @Description: 解码2级 TLV 16进制字符串
* @param tlvStr 16进制字符串
* @return Map<String,Map<String,String>> 键为tag,值为tag对应V的16进制字符串再进行tlv解析map,且子map值已将16进制转ascii码
*/
public static Map<String, Map<String, String>> decodeTLV2(String tlvStr) {
Map<String, Map<String, String>> pmap = new LinkedHashMap<String, Map<String, String>>();
Map<String, String> tpmap = decodeTLV(tlvStr);
for (String ptag : tpmap.keySet()) {
Map<String, String> cmap = new LinkedHashMap<String, String>();
Map<String, String> tcmap = decodeTLV(tpmap.get(ptag));
for (String ctag : tcmap.keySet()) {
if (StringUtils.isEmpty(tcmap.get(ctag))) {
cmap.put(ctag, tcmap.get(ctag));
continue;
}
cmap.put(ctag, new String(DatatypeConverter.parseHexBinary(tcmap.get(ctag))));
}
pmap.put(ptag, cmap);
}
return pmap;
}
public static void main(String[] args) {
String str = "A32801123230313730353038363637373838393930300212323031373035303831313232333334343535";
Map<String, Map<String, String>> m = decodeTLV2(str);
System.out.println(JSONObject.fromObject(m).toString());
String s = encodeTLV2(m);
System.out.println(s);
}
}