TLV协议格式是一种可变格式,
TLV的意思就是:Type类型, Lenght长度,Value值;
Type和Length的长度固定,一般那是2、4个字节; Value的长度有Length指定;
解析方法:
1.读取type 转换为ntohl、ntohs转换为主机字节序得到类型;指针偏移+2或4
2.读取lenght,转换为ntohl、ntohs转换为主机字节序得到长度;指针偏移+2或4
3.根据得到的长度读取value,指针偏移+Length;
TLV协议是一种通讯协议,一般将数据封装成TLV的形式,即Tag,Length,Value。
协议就是指通信双方对数据传输控制的一种规定,规定了数据格式,同步方式,传送速度,传送步骤的问题作出统一的规定。
可以理解为两个节点之间为了协同工作,协商一定的规则和约定。例如我们会规定字节序,各个字段类型等。
TLV 是一种可变的格式,其中:
T
可以理解为Tag
或Type
,用于标识标签或者编码格式信息;L
定义数值的长度;V
表示实际的数值。
T
和 L
的长度固定,一般是2或4个字节,V
的长度由 Length
指定。
图例帧格式如下所示:
tlv 协议基本算法如下
TLV 编码
public class BerTlvBuilder {
private static final Charset ASCII = Charset.forName("US-ASCII");
private static final BigDecimal HUNDRED = new BigDecimal(100);
private static final int DEFAULT_SIZE = 5 * 1024;
public BerTlvBuilder() {
this((BerTag)null);
}
public BerTlvBuilder(BerTag aTemplate) {
this(aTemplate, new byte[DEFAULT_SIZE], 0, DEFAULT_SIZE);
}
public BerTlvBuilder(BerTlvs tlvs) {
this((BerTag)null);
for (BerTlv tlv : tlvs.getList()) {
addBerTlv(tlv);
}
}
public BerTlvBuilder(BerTag aTemplate, byte[] aBuffer, int aOffset, int aLength) {
theTemplate = aTemplate;
theBuffer = aBuffer;
thePos = aOffset;
theBufferOffset = aOffset;
}
public static BerTlvBuilder from(BerTlv aTlv) {
return from(aTlv, DEFAULT_SIZE);
}
public static BerTlvBuilder from(BerTlv aTlv, int bufferSize) {
if(aTlv.isConstructed()) {
BerTlvBuilder builder = template(aTlv.getTag(), bufferSize);
for (BerTlv tlv : aTlv.theList) {
builder.addBerTlv(tlv);
}
return builder;
} else {
return new BerTlvBuilder(null, new byte[bufferSize], 0, bufferSize).addBerTlv(aTlv);
}
}
public static BerTlvBuilder template(BerTag aTemplate) {
return template(aTemplate, DEFAULT_SIZE);
}
public static BerTlvBuilder template(BerTag aTemplate, int bufferSize) {
return new BerTlvBuilder(aTemplate, new byte[bufferSize], 0, bufferSize);
}
public BerTlvBuilder addEmpty(BerTag aObject) {
return addBytes(aObject, new byte[]{}, 0, 0);
}
public BerTlvBuilder addByte(BerTag aObject, byte aByte) {
// type
int len = aObject.bytes.length;
System.arraycopy(aObject.bytes, 0, theBuffer, thePos, len);
thePos+=len;
// len
theBuffer[thePos++] = 1;
// value
theBuffer[thePos++] = aByte;
return this;
}
public BerTlvBuilder addAmount(BerTag aObject, BigDecimal aAmount) {
BigDecimal numeric = aAmount.multiply(HUNDRED);
StringBuilder sb = new StringBuilder(12);
sb.append(numeric.longValue());
while(sb.length() < 12) {
sb.insert(0, '0');
}
return addHex(aObject, sb.toString());
}
public BerTlvBuilder addDate(BerTag aObject, Date aDate) {
SimpleDateFormat format = new SimpleDateFormat("yyMMdd");
return addHex(aObject, format.format(aDate));
}
public BerTlvBuilder addTime(BerTag aObject, Date aDate) {
SimpleDateFormat format = new SimpleDateFormat("HHmmss");
return addHex(aObject, format.format(aDate));
}
public int build() {
if (theTemplate != null) {
int tagLen = theTemplate.bytes.length;
int lengthBytesCount = calculateBytesCountForLength(thePos);
// shifts array
System.arraycopy(theBuffer, theBufferOffset, theBuffer, tagLen + lengthBytesCount, thePos);
// copies tag
System.arraycopy(theTemplate.bytes, 0, theBuffer, theBufferOffset, theTemplate.bytes.length);
fillLength(theBuffer, tagLen, thePos);
thePos += tagLen + lengthBytesCount;
}
return thePos;
}
private void fillLength(byte[] aBuffer, int aOffset, int aLength) {
if(aLength < 0x80) {
aBuffer[aOffset] = (byte) aLength;
} else if (aLength <0x100) {
aBuffer[aOffset] = (byte) 0x81;
aBuffer[aOffset+1] = (byte) aLength;
} else if( aLength < 0x10000) {
aBuffer[aOffset] = (byte) 0x82;
aBuffer[aOffset+1] = (byte) (aLength / 0x100);
aBuffer[aOffset+2] = (byte) (aLength % 0x100);
} else if( aLength < 0x1000000 ) {
aBuffer[aOffset] = (byte) 0x83;
aBuffer[aOffset+1] = (byte) (aLength / 0x10000);
aBuffer[aOffset+2] = (byte) (aLength / 0x100);
aBuffer[aOffset+3] = (byte) (aLength % 0x100);
} else {
throw new IllegalStateException("length ["+aLength+"] out of range (0x1000000)");
}
}
private int calculateBytesCountForLength(int aLength) {
int ret;
if(aLength < 0x80) {
ret = 1;
} else if (aLength <0x100) {
ret = 2;
} else if( aLength < 0x10000) {
ret = 3;
} else if( aLength < 0x1000000 ) {
ret = 4;
} else {
throw new IllegalStateException("length ["+aLength+"] out of range (0x1000000)");
}
return ret;
}
public BerTlvBuilder addHex(BerTag aObject, String aHex) {
byte[] buffer = HexUtil.parseHex(aHex);
return addBytes(aObject, buffer, 0, buffer.length);
}
public BerTlvBuilder addBytes(BerTag aObject, byte[] aBytes) {
return addBytes(aObject, aBytes, 0, aBytes.length);
}
public BerTlvBuilder addBytes(BerTag aTag, byte[] aBytes, int aFrom, int aLength) {
int tagLength = aTag.bytes.length;
int lengthBytesCount = calculateBytesCountForLength(aLength);
// TAG
System.arraycopy(aTag.bytes, 0, theBuffer, thePos, tagLength);
thePos+=tagLength;
// LENGTH
fillLength(theBuffer, thePos, aLength);
thePos += lengthBytesCount;
// VALUE
System.arraycopy(aBytes, aFrom, theBuffer, thePos, aLength);
thePos+=aLength;
return this;
}
public BerTlvBuilder add(BerTlvBuilder aBuilder) {
byte[] array = aBuilder.buildArray();
System.arraycopy(array, 0, theBuffer, thePos, array.length);
thePos+=array.length;
return this;
}
public BerTlvBuilder addBerTlv(BerTlv aTlv) {
if(aTlv.isConstructed()) {
return add(from(aTlv, theBuffer.length));
} else {
return addBytes(aTlv.getTag(), aTlv.getBytesValue());
}
}
/**
* Add ASCII text
*
* @param aTag tag
* @param aText text
* @return builder
*/
public BerTlvBuilder addText(BerTag aTag, String aText) {
return addText(aTag, aText, ASCII);
}
/**
* Add ASCII text
*
* @param aTag tag
* @param aText text
* @return builder
*/
public BerTlvBuilder addText(BerTag aTag, String aText, Charset aCharset) {
byte[] buffer = aText.getBytes(aCharset);
return addBytes(aTag, buffer, 0, buffer.length);
}
public BerTlvBuilder addIntAsHex(BerTag aObject, int aCode, int aLength) {
StringBuilder sb = new StringBuilder(aLength*2);
sb.append(aCode);
while(sb.length()<aLength*2) {
sb.insert(0, '0');
}
return addHex(aObject, sb.toString());
}
public byte[] buildArray() {
int count = build();
byte[] buf = new byte[count];
System.arraycopy(theBuffer, 0, buf, 0, count);
return buf;
}
public BerTlv buildTlv() {
int count = build();
return new BerTlvParser().parseConstructed(theBuffer, theBufferOffset, count);
}
public BerTlvs buildTlvs() {
int count = build();
return new BerTlvParser().parse(theBuffer, theBufferOffset, count);
}
private final int theBufferOffset;
private int theLengthPosition;
private int thePos;
private final byte[] theBuffer;
private final BerTag theTemplate;
}
TLV解码
public class BerTlvParserV2 {
private static final BerTagFactory DEFAULT_TAG_FACTORY = new DefaultBerTagFactory();
private final BerTagFactory tagFactory;
private final IBerTlvLogger log;
public BerTlvParserV2() {
this(DEFAULT_TAG_FACTORY, EMPTY_LOGGER);
}
public BerTlvParserV2(IBerTlvLogger aLogger) {
this(DEFAULT_TAG_FACTORY, aLogger);
}
public BerTlvParserV2(BerTagFactory aTagFactory) {
this(aTagFactory, EMPTY_LOGGER);
}
public BerTlvParserV2(BerTagFactory aTagFactory, IBerTlvLogger aLogger) {
tagFactory = aTagFactory;
log = aLogger;
}
public BerTlv parseConstructed(byte[] aBuf) {
return parseConstructed(aBuf, 0, aBuf.length);
}
public BerTlv parseConstructed(byte[] aBuf, int aOffset, int aLen) {
ParseResult result = parseWithResult(0, aBuf, aOffset, aLen);
return result.tlv;
}
public BerTlvs parse(byte[] aBuf) {
return parse(aBuf, 0, aBuf.length);
}
public BerTlvs parse(byte[] aBuf, final int aOffset, int aLen) {
List<BerTlv> tlvs = new ArrayList<BerTlv>();
if (aLen == 0) return new BerTlvs(tlvs);
int offset = aOffset;
while (offset < aLen - 2) {
ParseResult result = parseWithResult(0, aBuf, offset, aLen - offset);
tlvs.add(result.tlv);
if (result.offset >= aOffset + aLen) {
break;
}
offset = result.offset;
}
return new BerTlvs(tlvs);
}
private ParseResult parseWithResult(int aLevel, byte[] aBuf, int aOffset, int aLen) {
String levelPadding = createLevelPadding(aLevel);
if (aOffset + aLen > aBuf.length) {
throw new IllegalStateException("Length is out of the range [offset=" + aOffset + ", len=" + aLen + ", array.length=" + aBuf.length + ", level=" + aLevel + "]");
}
if (log.isDebugEnabled()) {
log.debug("{}parseWithResult(level={}, offset={}, len={}, buf={})", levelPadding, aLevel, aOffset, aLen, HexUtil.toFormattedHexString(aBuf, aOffset, aLen));
}
// tag
int tagBytesCount = getTagBytesCount(aBuf, aOffset);
BerTag tag = createTag(levelPadding, aBuf, aOffset, tagBytesCount);
if (log.isDebugEnabled()) {
log.debug("{}tag = {}, tagBytesCount={}, tagBuf={}", levelPadding, tag, tagBytesCount, HexUtil.toFormattedHexString(aBuf, aOffset, tagBytesCount));
}
// length
int lengthBytesCount = getLengthBytesCount(aBuf, aOffset + tagBytesCount);
int valueLength = getDataLength(aBuf, aOffset + tagBytesCount);
if (log.isDebugEnabled()) {
log.debug("{}lenBytesCount = {}, len = {}, lenBuf = {}"
, levelPadding, lengthBytesCount, valueLength, HexUtil.toFormattedHexString(aBuf, aOffset + tagBytesCount, lengthBytesCount));
}
// value
if (tag.isConstructed()) {
ArrayList<BerTlv> list = new ArrayList<BerTlv>();
addChildren(aLevel, aBuf, aOffset + tagBytesCount + lengthBytesCount, levelPadding, lengthBytesCount, valueLength, list);
int resultOffset = aOffset + tagBytesCount + lengthBytesCount + valueLength;
if (log.isDebugEnabled()) {
log.debug("{}returning constructed offset = {}", levelPadding, resultOffset);
}
return new ParseResult(new BerTlv(tag, list), resultOffset);
} else {
// value
byte[] value = new byte[valueLength];
System.arraycopy(aBuf, aOffset + tagBytesCount + lengthBytesCount, value, 0, valueLength);
int resultOffset = aOffset + tagBytesCount + lengthBytesCount + valueLength;
if (log.isDebugEnabled()) {
log.debug("{}value = {}", levelPadding, HexUtil.toFormattedHexString(value));
log.debug("{}returning primitive offset = {}", levelPadding, resultOffset);
}
return new ParseResult(new BerTlv(tag, value), resultOffset);
}
}
/**
* @param aLevel level for debug
* @param aBuf buffer
* @param aOffset offset (first byte)
* @param levelPadding level padding (for debug)
* @param aDataBytesCount data bytes count
* @param valueLength length
* @param list list to add
*/
private void addChildren(int aLevel, byte[] aBuf, int aOffset, String levelPadding, int aDataBytesCount, int valueLength, ArrayList<BerTlv> list) {
int startPosition = aOffset;
int len = valueLength;
while (startPosition < aOffset + valueLength) {
ParseResult result = parseWithResult(aLevel + 1, aBuf, startPosition, len);
list.add(result.tlv);
startPosition = result.offset;
len = (aOffset + valueLength) - startPosition;
if (log.isDebugEnabled()) {
log.debug("{}level {}: adding {} with offset {}, startPosition={}, aDataBytesCount={}, valueLength={}"
, levelPadding, aLevel, result.tlv.getTag(), result.offset, startPosition, aDataBytesCount, valueLength);
}
}
}
private String createLevelPadding(int aLevel) {
if (!log.isDebugEnabled()) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < aLevel * 4; i++) {
sb.append(' ');
}
return sb.toString();
}
private static class ParseResult {
public ParseResult(BerTlv aTlv, int aOffset) {
tlv = aTlv;
offset = aOffset;
}
@Override
public String toString() {
return "ParseResult{" +
"tlv=" + tlv +
", offset=" + offset +
'}';
}
private final BerTlv tlv;
private final int offset;
}
private BerTag createTag(String aLevelPadding, byte[] aBuf, int aOffset, int aLength) {
if (log.isDebugEnabled()) {
log.debug("{}Creating tag {}...", aLevelPadding, HexUtil.toFormattedHexString(aBuf, aOffset, aLength));
}
return tagFactory.createTag(aBuf, aOffset, aLength);
}
private int getTagBytesCount(byte[] aBuf, int aOffset) {
if ((aBuf[aOffset] & 0x1F) == 0x1F) { // see subsequent bytes
int len = 2;
for (int i = aOffset + 1; i < aOffset + 10; i++) {
if ((aBuf[i] & 0x80) != 0x80) {
break;
}
len++;
}
return len;
} else {
return 1;
}
}
private int getDataLength(byte[] aBuf, int aOffset) {
int length = aBuf[aOffset] & 0xff;
if ((length & 0x80) == 0x80) {
int numberOfBytes = length & 0x7f;
if (numberOfBytes > 3) {
throw new IllegalStateException(String.format("At position %d the len is more then 3 [%d]", aOffset, numberOfBytes));
}
length = 0;
for (int i = aOffset + 1; i < aOffset + 1 + numberOfBytes; i++) {
length = length * 0x100 + (aBuf[i] & 0xff);
}
}
return length;
}
private static int getLengthBytesCount(byte aBuf[], int aOffset) {
int len = aBuf[aOffset] & 0xff;
if ((len & 0x80) == 0x80) {
return 1 + (len & 0x7f);
} else {
return 1;
}
}
private static final IBerTlvLogger EMPTY_LOGGER = new IBerTlvLogger() {
public boolean isDebugEnabled() {
return false;
}
public void debug(String aFormat, Object... args) {
}
};
}