文章目录
BER-TLV
BER-TLV中BER是Basic Encoding Rules的简写,是ASN.1的一种编码格式,它是将抽象信息编码成为具体数据流的最初规则。
相关知识及定义来自:X.690
BER编码
BER格式描述了自我描述与分界的数据结构。每个数据编码为一个由type标识,length描述,data数据的元素,并且在必要的位置添加结束标识。这个格式可以在不需要提前了解数据的大小,内容,及数据意义情况下允许接收方从数据流(data inputstream)中解码数据。
BER格式
这是4个部分组成的一个单元组,末尾的八元组(字节)是可选的,只在长度形式未定义情况下使用。内容(Contents octets)部分也可能在为null的情况下被移除。
Tag/Type域
Type
这部分数据(尤其是序列(sequences),集合(sets),选项(choices)的成员)由唯一tag数标识,区别于整个data中的其他成员。这样的标记可以是隐式的(在这种情况下,它们被编码为值的TLV标记,而不是使用基类型作为TLV标记)或显式(其中标记用于包装基类型TLV的构造TLV中)。
编码可以是基本结构类型或是复杂类型的,取决于选择的类型。下边图描述了tag类型的选择,这些定义也是在ASN.1定义中声明的。
tag域编码
识别部分将元素类型编码为ASN.1的tag值,由类型与数字组成,这里也定义了内容部分是基本结构(primitive)或是复杂结构(constructed)。
首个字节中,bit6编码指示了类型是基本类型或是复合类型,bit7, bit8指示了类型,而big1-bit5指示了tag值,也可以有后续字节一同标识tag部分。
当tag数值太大,超出tag域5位bit(不超过31)可表达的范围时,需要更多字节表示tag。
当首字节中bit1-bit5均是1,tag数值由后续字节表示。当后续字节的bit8位置是1时,表明还有后续字节,bit1-bit7表示tag数值。
tag二进制是大端的(即最高位在左边)
Length域
length域有两个格式的定义:定义格式,未定义格式。
定义格式(Definite)
这部分编码的值表明了内容部分(Content octets)基本结构或复杂结构占的长度。不同长度值的表示范围由一个长格式或一个短格式进行定义。
- 短格式:一个字节,bit8是0,bit1-bit7编码值表示content部分占用的字节数;
- 长格式:若干字节,首字节bit8是1,表示后续还有1个或多个字节表示长度值。首字节bit8是1,bit1-bit7(排除0和127)编码值表示内容部分的长度。
未定义格式(Indefinite)
这个格式未编码长度值,但内容部分需要标记字节来结束。
这个格式有一个字节组成,其中bit8是1,bit1-bit7均是0。后续内容部分就是2个字节的EOC符号。
Content域
这个部分的编码值表示了元素的实际数据值。
注意的是值域(value field)也可能没有字节表示,即length域的编码值为0。
Java解析BER-TLV方法封装
Tlv
/**
* Tlv接口定义,用以自定义TLV实体类进行实现。
*
* TLV(type-length-value or tag-length-value) is an encoding theme for optional information
* element in a certain protocol.
*
* The type and length are fixed in size(typically 1-4 bytes) and the value field is of variable
* size.
*
* Type: A binary code, often simply alphanumeric, which indicates the kind of field that this
* part of the message represents.
*
* Length: The size of the value field(typically in bytes).
*
* Value: Variable-sized series of bytes which contains data for this part of the message.
*
* @author Nicholas Ni
* @since 2020/8/3 16:37
*/
public class Tlv implements Serializable {
}
BerTlv
/**
* 基本编码规则TLV格式,符合ASN.1标准。
*
* 若需要自定义相关的数据处理,可以自定义实体类集成Tlv,且解析类实现TlvParser接口并实现parseTlvList(byte[])方法。
*/
public class BerTlv extends Tlv {
public int tag;
public int length;
public byte[] value;
public BerTlv() {
}
public BerTlv(int tag, int length, byte[] value) {
this.tag = tag;
this.length = length;
this.value = value;
}
}
TlvParser
/**
* TLVResolvable定义了TLV解析方法,提供默认的解析方式。
* 由于TLV数据type,length字节数占1-4个字节,可以是变长的,因此在解析前需要设置具体的字节长度——依据约定。
*/
public interface TlvParser extends Resolvable {
/**
* 解析TLV列表。
*
* @param tlvByteArray TLV字节数组
*/
default Map<Integer, ? extends Tlv> parseTlvList(byte[] tlvByteArray) {
System.out.println("This is default implementation.");
return new TreeMap<>();
}
}
BerTlvParser
/**
* 解析TLV数据工具类。
* <p>
* 使用:
* <p>
* <p>
* 直接使用创建,匿名类方式进行创建,可以重写部分方法,灵活设置type,length长度。
*/
public final class BerTlvParser implements TlvParser {
/**
* 依据byte数组解析TLV列表数据。
*
* @param data 包含TLV数据的字节数组
* @param offset TLV数据的开始索引
* @param length TLV数据长度
* @return tlv map
*/
public Map<Integer, BerTlv> parseTLVList(byte[] data, int offset, int length) {
byte[] tlvByteArray = new byte[length];
System.arraycopy(data, offset, tlvByteArray, 0, length);
return parseTlvList(tlvByteArray);
}
/**
* 依据byte数组解析TLV列表数据。
*
* @param data TLV字节数组
* @return TLV map
*/
@Override
public final Map<Integer, BerTlv> parseTlvList(byte[] data) {
Objects.requireNonNull(data, "Byte array indicating TLV list can not be null!");
if (data.length == 0) {
return new TreeMap<>();
}
int currIndex = -1;
int tag = data[++currIndex] & 0xFF;
if ((tag & 0x20) != 0x20) {
// 解析基本结构类型(单一结构)
return parsePrimitiveData(data);
}
// 解析复合结构类型
return parseComprehensiveData(data);
}
/**
* 解析复合类型的TLV数据结构 —— 即嵌套的TLV结构。
*
* @param data 解析的数据
* @return 完成解析的TLV map
*/
private Map<Integer, BerTlv> parseComprehensiveData(byte[] data) {
final Map<Integer, BerTlv> tlvMap = new TreeMap<>();
parseComprehensiveData(data, tlvMap, 0);
System.out.println(
String.format(Locale.getDefault(),
"Parsed result size: %d",
tlvMap.size())
);
return tlvMap;
}
/**
* 解析复杂类型TLV结构。
*
* @param data 解析的数据
* @param retMap 存储结果的TLV map
* @param index 当前索引位置
*/
private void parseComprehensiveData(byte[] data,
Map<Integer, BerTlv> retMap,
int index) {
int currIndex = index;
if (currIndex >= data.length) {
return;
}
final int lengthTag = sizeTag(data, currIndex);
currIndex += lengthTag;
final int lengthLen = sizeLength(data, currIndex);
currIndex += lengthLen;
int currentByte = data[currIndex] & 0xFF;
// 单一结构
while ((currentByte & 0x20) != 0x20) {
currIndex += parsePrimitiveData(data, retMap, currIndex);
if (currIndex >= data.length) {
break;
}
currentByte = data[currIndex] & 0xFF;
}
if (currIndex >= data.length) {
return;
}
parseComprehensiveData(data, retMap, currIndex);
}
/**
* 解析复合结构内单一结构(无法再分)的TLV数据。
*
* @param data 解析的数据
* @param retMap 存储结果的TLV map
* @param index 当前索引位置
* @return 当前TLV实体的长度
*/
private int parsePrimitiveData(byte[] data,
Map<Integer, BerTlv> retMap,
int index) {
if (index >= data.length) {
return 0;
}
final BerTlv berTlv = new BerTlv();
int currIndex = index;
final int lengthOfTag = sizeTag(data, currIndex);
berTlv.tag = parseTagNumber(data, currIndex);
currIndex += lengthOfTag;
final int lengthOfLength = sizeLength(data, currIndex);
berTlv.length = parseLength(data, currIndex);
currIndex += lengthOfLength;
final int lengthOfValue = berTlv.length;
berTlv.value = parseValueArray(data, currIndex, lengthOfValue);
currIndex += lengthOfValue;
retMap.put(berTlv.tag, berTlv);
return currIndex - index;
}
/**
* 解析单一结构(primitive constructed data) TLV数据。
*
* @param data TLV数据
* @return 解析后的TLV map
*/
private Map<Integer, BerTlv> parsePrimitiveData(byte[] data) {
final BerTlv berTlv = new BerTlv();
int currIndex = 0;
final int lengthOfTag = sizeTag(data, currIndex);
berTlv.tag = parseTagNumber(data, currIndex);
currIndex += lengthOfTag;
final int lengthOfLength = sizeLength(data, currIndex);
berTlv.length = parseLength(data, currIndex);
currIndex += lengthOfLength;
final int lengthOfValue = berTlv.length;
berTlv.value = parseValueArray(data, currIndex, lengthOfValue);
currIndex += lengthOfValue;
System.out.println(String.format(Locale.getDefault(), "final index: %d", currIndex));
if (currIndex < data.length) {
throw new RuntimeException(
"Parse primitive constructed data, value length does not match length field value."
);
}
return new TreeMap<Integer, BerTlv>() {
{
put(berTlv.tag, berTlv);
}
};
}
/**
* 解析获取tlv字节数组中表示value的字节数组,返回原始的字节数组。
*
* @param data TLV字节数组
* @param offset 当前索引
* @param length 表示value的字节数组的长度
* @return 表示value的字节数组
*/
private byte[] parseValueArray(byte[] data, int offset, int length) {
byte[] valueArray = new byte[length];
System.arraycopy(data, offset, valueArray, 0, length);
return valueArray;
}
/**
* 解析tag域,并返回tag域的值。
* <p>
* 如: 4F 05 48656C6C6F
* 解析过程:
* <ul>
* <li>'4F'(01001111) 是tag域的字段,其高三位中前两位表示解析的信息类型,第三位表示TLV是基本结构类型或是复合类型的结构,暂且不计,用途不影响解析;</li>
* <li>低5位(01111),最高位若不是1,则剩余4位即可标识TLV实体的标号;若5位均是1(11111),则表示后续还有tag域的字节;</li>
* </ul>
*
* @param data TLV字节数组
* @param offset 当前偏移值(索引)
* @return tag值
*/
private int parseTagNumber(byte[] data, int offset) {
int byteTag = data[offset] & 0xFF;
if ((byteTag & 0x1F) < 0x1F) {
return byteTag & 0x1F;
}
int indexTag = offset + 1;
byteTag = data[indexTag] & 0xFF;
while (((byteTag >>> 7) & 0x01) == 0x01) {
byteTag = data[++indexTag];
}
int tagNumber = 0;
// 表示tag number的字节数
int lenTagNumber = indexTag - offset;
byte[] tagNumberByes = new byte[lenTagNumber];
System.arraycopy(data, offset + 1, tagNumberByes, 0, lenTagNumber);
switch (lenTagNumber) {
case 1: {
tagNumber = tagNumberByes[0] & 0xFF;
break;
}
case 2: {
tagNumber = ((tagNumberByes[0] & 0xFF) << 8) | (tagNumberByes[1] & 0xFF);
break;
}
case 3: {
tagNumber = ((tagNumberByes[0] & 0xFF) << 16) |
((tagNumberByes[1] & 0xFF) << 8) | (tagNumberByes[2] & 0xFF);
break;
}
case 4: {
tagNumber = ((tagNumberByes[0] & 0xFF) << 24) |
((tagNumberByes[1] & 0xFF) << 16) |
((tagNumberByes[2] & 0xFF) << 8) |
(tagNumberByes[2] & 0xFF);
break;
}
}
if (tagNumber < 0x1F) {
throw new RuntimeException(
"Invalid tag number, code < 31, but len of tag field is " + (lenTagNumber + 1)
+ ", index = " + offset
);
}
return tagNumber;
}
/**
* 解析获取length域的值,即tag-length-value中length值。
*
* @param data TLV字节数组
* @param offset 当前偏移值(索引)
* @return length值
*/
private int parseLength(byte[] data, int offset) {
int byteLength = data[offset] & 0xFF;
if (byteLength < 0x80) {
return byteLength & 0x7F;
}
if (byteLength == 0x80) {
throw new RuntimeException("Invalid length field code = 0x80!");
}
final int numLenBytes = byteLength & 0x7F;
byte[] lengthBytes = new byte[numLenBytes];
System.arraycopy(data, offset + 1, lengthBytes, 0, numLenBytes);
int valLength = 0;
switch (lengthBytes.length) {
case 1: {
valLength = lengthBytes[0] & 0xFF;
break;
}
case 2: {
valLength = ((lengthBytes[0] & 0xFF) << 8) | (lengthBytes[1] & 0xFF);
break;
}
case 3: {
valLength = ((lengthBytes[0] & 0xFF) << 16) |
((lengthBytes[1] & 0xFF) << 8) |
(lengthBytes[2] & 0xFF);
break;
}
case 4: {
valLength = ((lengthBytes[0] & 0xFF) << 24) |
((lengthBytes[1] & 0xFF) << 16) |
((lengthBytes[2] & 0xFF) << 8) |
(lengthBytes[3] & 0xFF);
break;
}
}
return valLength;
}
/**
* 解析tag域长度,判断依据:
* </p>
* <ul>
* <li>判断tag域首字节低5位是否全为1,若全是1 则表示tag域还有后续字节表示tag编号;</li>
* <li>若有后续字节,查看最高位,若最高位是1,则表示后续依然有字节表示tag,0则结束。</li>
* </ul>
*
* @param data tlv字节数组
* @param offset 当前偏移位(索引位置)
* @return tag域占的字节数
*/
private int sizeTag(byte[] data, int offset) {
int indexTag = offset;
int byteTag = data[indexTag] & 0xFF;
if ((byteTag & 0x1F) == 0x1F) {
byteTag = data[++indexTag] & 0xFF;
while (((byteTag >>> 7) & 0x01) == 1) {
byteTag = data[++indexTag] & 0xFF;
}
}
return indexTag - offset + 1;
}
/**
* BER-TLV中的长度表示Value域中的数据长度,由1到多个字节组成。
* <p>
* 解析length域占的字节数,判断依据:
* <ul>
* <li>如果首字节的最高位为0,则低7位表示长度,数据长度最大值为127;</li>
* <li>如果首字节的最高位为1,则表明Value域中的数据长度超过127,其低7位表示后续LEN域的字节数。</li>
* </ul>
*
* @param data tlv字节数组
* @param offset 当前偏移位(索引位置)
* @return length域占的字节数
*/
private int sizeLength(byte[] data, int offset) {
int byteLength = data[offset] & 0xFF;
int sizeLength = 1;
if (((byteLength >>> 7) & 0x01) == 0x01) {
sizeLength += (byteLength & 0x7F);
}
return sizeLength;
}
}