文章目录

  • BER-TLV
    • BER编码
    • BER格式
    • Tag/Type域
      • Type
      • tag域编码
    • Length域
      • 定义格式(Definite)
      • 未定义格式(Indefinite)
    • Content域
  • Java解析BER-TLV方法封装

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*/@Overridepublic 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;}
}

BER-TLV的认识与原子数据解析相关推荐

  1. pymavlink 源码剖析(一)之XML文件的数据解析

    文章目录 1 引言 2 pymavlink 的代码自动生成方法 3 XML 文件的数据解析 3.1 XML 文件预处理 3.2 解析 XML 的数据 3.2.1 依据协议版本初始化一些版本特征变量 3 ...

  2. TLV自定义通信协议的编码和解析

    以下内容均为原创,如有错误,欢迎指正,感激!!! 所有代码都在我的github: https://github.com/zhanghang1999/Linux/tree/master/TLV 说到通信 ...

  3. 爬虫之常用数据解析方法

    爬虫之常用数据解析方法

  4. iOS - XML 数据解析

    前言 @interface NSXMLParser : NSObjectpublic class NSXMLParser : NSObject 1.XML 数据 XML(Extensible Mark ...

  5. php接口 汉字出错 空,php接口开发时,数据解析失败问题,字符转义,编码问题(示例代码)...

    php接口开发时,数据解析失败问题,字符转义,编码问题 情景: A平台--->向接口请求数据---->接口向B平台请求数据---->B平台返回数据给接口---->接口返回数据给 ...

  6. Android JSON数据解析(GSON方式)

    要创建和解析JSON数据,也可以使用GSON来完成.GSON是Google提供的用来在Java对象和JSON数据之间进行映射的Java类库.使用GSON,可以很容易的将一串JSON数据转换为一个Jav ...

  7. Android学习之JSON数据解析

    在Android应用开发中,常用的数据交换格式有XML和JSON,这两种方式各有各的好处,我们在特定的应用开发中可以选择合适的一种.下面来看一下JOSN数据解析: 例子永远是最好的教程,下面我们来看个 ...

  8. 【2020/6/24整理版】利用csi tool获取csi数据并进行数据解析----适合初学的小白

    1.准备工作 <1> 查看自己电脑是否能安装Intel 5300网卡,不能安装的话直接看文章最后,尝试Atheros csi tool工具. <2> 安装Ubuntu系统,cs ...

  9. 歌词数据解析、歌词滚动、歌词进度控制功能的实现(基于js-base64、lyric-parser、better-scroll),以vue项目为例...

    歌词数据解析.歌词滚动.歌词进度控制功能的实现(基于js-base64.lyric-parser.better-scroll) 1.需求分析 后台歌词接口返回的数据如下(base64字符串): W3R ...

最新文章

  1. DAS、NAS、SAN、iSCSI 存储方案概述
  2. Application provided invalid, non monotonically increasing dts to muxer in stream 0: -92233720368547
  3. 010_logback中的SocketAppender
  4. Unity3D重要知识点
  5. 批处理命令 / rem :: :
  6. UWP 保存用户设置
  7. 【POJ2887】Big String(块状链表,模板)
  8. python复杂网络库networkx:基础
  9. mysql 5.6的安装
  10. 使用windbg 检查c++程序死锁
  11. Easypoi 报表模板设置
  12. 仿蓝色理想网站的导航菜单
  13. csp ccf公共钥匙盒
  14. 金仓数据库 KingbaseES Sys_repack 解决金仓数据库 KingbaseES 表膨胀的问题
  15. SCP,NFS,TFTP的初步认识
  16. 天眼查 Authorized和企查查 sign破解
  17. 微信调试、手机QQ调试、Qzone之x5内核inspect调试解决方案
  18. Perl(五)Perl的反引号
  19. AVS全面学习[ZT]
  20. python基础(持续更新)

热门文章

  1. linux桌面没有我的电脑,电脑不显示桌面,没有图标,右击桌面所有地方也没有用,怎么解决?...
  2. 基于浏览器 webrtc的PC屏幕共享
  3. 微信授权 10003
  4. oracle版本历史
  5. 共阴、共阳数码管的详解
  6. excel日期格式改不了_日期还在手动数?学会自动计算,节约90%的时间
  7. 33 | MySQL全表扫描会将内存打爆?(看了这篇你就赚)
  8. Android 项目必备(十六)--> 手机号 验证码 密码
  9. itext生成PDF,天坑
  10. IDEA 初次使用菜鸟教程