TLV即Tag-Length-Value,常在IC卡与POS终端设备中通过这样的一个应用通信协议进行数据交换。在金融系统以及认证中,PBOC以及
EMV的认证规范文档上面也有对TLV做了一些说明,由于认证规范都是英文文档,所以有些人可能不易于理解。首先我先介绍下什么是TLV,TLV的用途是
什么,以及如何实现它的打包解包算法。
金融系统中的TLV是BER-TLV编码的一个特例编码规范,而BER-TLV是ISO定义中的规范。在TLV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)。这里的长度域的值实际上就是内容域的长度。
其实,在BER编码的方式有两种情况,一种是确定长度的方式,一种是不确定长度的方式,而金融TLV选择了确定长度的方式,这样在设备之间的数据传输量上就可以减少。
Tag域说明:
先来看下TLV的Tag是如何编码的,先看下图:
这张图说明了Tag域第一个字节的编码格式。其中b8-b1代表1个字节中的8个位。
其中b8,b7代表数据的类别。根据2个位的组合,有四种类别:通用类别,应用类别,上下文语境类别,专用类别。这个主要用于在于终端设备交互的时候,确定数据处理的类型。
b6代表的是数据元结构,也就是说它是属于简单数据元结构,还是属于结构(复合)数据元结构。当b6为1的时候,就需要后续的字节进行扩展。也就是说复合的TLV中,value域里也包含一个或多个TLV,这个稍后接着介绍。
当b5-b1代表串行号,当5个位都为1时,需要将tag域扩展到下一个字节中,也就是Tag占2个字节;而当5个位都不全为1时,该Tag域就只占1个字节。
现在,看下b5-b1:11111的情况:
从图中我们看到BER-TLV编码中,当b8为1时,Tag还需要有后续字节,直到b8为0为止。从EMV文档中的说明,Tag最多只占用2个字节,所以这样就相对比较简单一些了。当b8为0时,该Tag域结束,总共就占用2个字节。
Length域说明:
在文档中没有图片叙述,我自绘一个:
当b8为0时,该字节的b7-b1作为value域的长度;当b8为1时,b7-b1作为后续字节的长度,也就是说,例如有这样一个
值:10000011,代表后续还有3个字节作为value域的长度(本字节不算,本字节变为作为一个Length的索引)。3个字节代表value的长
度,意味着什么呢,意味着内容的长度当需要很大的时候,字节的位数就会跟着越高,3个字节就代表最大可以有256*256*256的长度。
Value域说明:
也是分成两种情况考虑,就是前面说到的Tag分成两个数据元结构,一种是简单数据元结构,一种是复合数据元架构:
先来看看简单数据元结构:
Tag就是Tag,没有子标签Tag,基本结构就是T-L-V。
再看下符合数据元结构:
后面的Value说明:Primitive or constructed BER-TLV data object number,包含一个简单数据元结构或者也可以是一个符合数据元结构。这样可以看出,算法中必须要实现Tag的嵌套功能,递归算法不可少。
算法实现:
根据以上的说明现在来实现它的打包解包的算法(打包的目的是将一个从终端上发的请求数据——字节数组,构造成一系列的TLV结构实体;解包的目的刚好相反,就是将TLV结构实体解析成字节数组,然后通过IC卡发送到终端上)。
首先定义一个TLV结构实体:
7 |
unsigned int LengthSize; |
8 |
TLVEntity* Sub_TLVEntity; |
其
中TagSize代表Tag字段的字节长度,LengthSize代表Length的字节长度,这里的Length记住要使用char*,由于前面说
过,Length可能包含多个字节,通过多个字节确定Value域的长度,Sub_TLVEntity作为子嵌套的TLV结构体。
定义一个TLVPackage的打包类:
TLVPackage.h:
06 |
virtual ~TLVPackage(); |
08 |
static void Construct(unsigned char * buffer, unsigned int bufferLength, TLVEntity* tlvEntity, unsigned int & entityLength, unsigned int status=0); |
10 |
static void Parse(TLVEntity* tlvEntity, unsigned int entityLength, unsigned char * buffer, unsigned int & bufferLength); |
具体方法实现:
002 |
void TLVPackage:: Construct( |
003 |
unsigned char * buffer, |
004 |
unsigned int bufferLength, |
005 |
TLVEntity* tlvEntity, |
006 |
unsigned int & entityLength, |
010 |
int currentTLVIndex = 0; |
011 |
int currentIndex = 0; |
012 |
int currentStatus = 'T' ; |
013 |
unsigned long valueSize = 0; |
015 |
while (currentIndex < bufferLength) |
017 |
switch (currentStatus) |
022 |
if ((status == 1 && buffer[currentIndex] & 0x20) != 0x20) |
024 |
tlvEntity[currentTLVIndex].Sub_TLVEntity = NULL; |
026 |
if ((buffer[currentIndex] & 0x1f) == 0x1f) |
028 |
int endTagIndex = currentIndex; |
029 |
while ((buffer[++endTagIndex] & 0x80) == 0x80); |
030 |
int tagSize = endTagIndex - currentIndex + 1; |
032 |
tlvEntity[currentTLVIndex].Tag = new unsigned char [tagSize]; |
033 |
memcpy (tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, tagSize); |
034 |
tlvEntity[currentTLVIndex].Tag[tagSize] = 0; |
036 |
tlvEntity[currentTLVIndex].TagSize = tagSize; |
038 |
currentIndex += tagSize; |
042 |
tlvEntity[currentTLVIndex].Tag = new unsigned char [1]; |
043 |
memcpy (tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, 1); |
044 |
tlvEntity[currentTLVIndex].Tag[1] = 0; |
046 |
tlvEntity[currentTLVIndex].TagSize = 1; |
054 |
if ((buffer[currentIndex] & 0x1f) == 0x1f) |
056 |
int endTagIndex = currentIndex; |
057 |
while ((buffer[++endTagIndex] & 0x80) == 0x80); |
058 |
int tagSize = endTagIndex - currentIndex + 1; |
060 |
tlvEntity[currentTLVIndex].Tag = new unsigned char [tagSize]; |
061 |
memcpy (tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, tagSize); |
062 |
tlvEntity[currentTLVIndex].Tag[tagSize] = 0; |
064 |
tlvEntity[currentTLVIndex].TagSize = tagSize; |
066 |
currentIndex += tagSize; |
070 |
tlvEntity[currentTLVIndex].Tag = new unsigned char [1]; |
071 |
memcpy (tlvEntity[currentTLVIndex].Tag, buffer + currentIndex, 1); |
072 |
tlvEntity[currentTLVIndex].Tag[1] = 0; |
074 |
tlvEntity[currentTLVIndex].TagSize = 1; |
083 |
if ((buffer[currentIndex] & 0x80) == 0x80) |
085 |
for ( int index = 0; index < 2; index++) |
087 |
subLength += buffer[currentIndex + 1 + index] << (index * 8); |
090 |
temp = new unsigned char [subLength]; |
092 |
memcpy (temp, buffer + currentIndex + 3, subLength); |
096 |
subLength = buffer[currentIndex]; |
098 |
temp = new unsigned char [subLength]; |
100 |
memcpy (temp, buffer + currentIndex + 1, subLength); |
105 |
unsigned int oLength; |
106 |
tlvEntity[currentTLVIndex].Sub_TLVEntity = new TLVEntity[1]; |
107 |
Construct(temp, subLength, tlvEntity[currentTLVIndex].Sub_TLVEntity, oLength); |
114 |
if ((buffer[currentIndex] & 0x80) != 0x80) |
116 |
tlvEntity[currentTLVIndex].Length = new unsigned char [1]; |
117 |
memcpy (tlvEntity[currentTLVIndex].Length, buffer + currentIndex, 1); |
118 |
tlvEntity[currentTLVIndex].Length[1] = 0; |
119 |
tlvEntity[currentTLVIndex].LengthSize = 1; |
121 |
valueSize = tlvEntity[currentTLVIndex].Length[0]; |
129 |
unsigned int lengthSize = buffer[currentIndex] & 0x7f; |
133 |
for ( int index = 0; index < lengthSize; index++) |
135 |
valueSize += buffer[currentIndex + index] << (index * 8); |
138 |
tlvEntity[currentTLVIndex].Length = new unsigned char [lengthSize]; |
139 |
memcpy (tlvEntity[currentTLVIndex].Length, buffer + currentIndex, lengthSize); |
140 |
tlvEntity[currentTLVIndex].Length[lengthSize] = 0; |
142 |
tlvEntity[currentTLVIndex].LengthSize = lengthSize; |
144 |
currentIndex += lengthSize; |
150 |
tlvEntity[currentTLVIndex].Value = new unsigned char [valueSize]; |
151 |
memcpy (tlvEntity[currentTLVIndex].Value, buffer + currentIndex, valueSize); |
152 |
tlvEntity[currentTLVIndex].Value[valueSize] = 0; |
154 |
currentIndex += valueSize; |
157 |
currentTLVIndex += 1; |
166 |
entityLength = currentTLVIndex; |
170 |
void TLVPackage::Parse( |
171 |
TLVEntity* tlvEntity, |
172 |
unsigned int entityLength, |
173 |
unsigned char * buffer, |
174 |
unsigned int & bufferLength |
177 |
int currentIndex = 0; |
178 |
int currentTLVIndex = 0; |
179 |
unsigned long valueSize = 0; |
181 |
while (currentTLVIndex < entityLength) |
184 |
TLVEntity entity = tlvEntity[currentTLVIndex]; |
186 |
memcpy (buffer + currentIndex, entity.Tag, entity.TagSize); |
187 |
currentIndex += entity.TagSize; |
189 |
for ( int index = 0; index < entity.LengthSize; index++) |
191 |
valueSize += entity.Length[index] << (index * 8); |
195 |
buffer[currentIndex] = 0x80 | entity.LengthSize; |
199 |
memcpy (buffer + currentIndex, entity.Length, entity.LengthSize); |
200 |
currentIndex += entity.LengthSize; |
202 |
if (entity.Sub_TLVEntity == NULL) |
204 |
memcpy (buffer + currentIndex, entity.Value, valueSize); |
205 |
currentIndex += valueSize; |
209 |
unsigned int oLength; |
210 |
Parse(entity.Sub_TLVEntity, 1, buffer + currentIndex, oLength); |
211 |
currentIndex += oLength; |
216 |
buffer[currentIndex] = 0; |
217 |
bufferLength = currentIndex; |
然后写测试程序:
02 |
unsigned char requestBuf[] = { |
03 |
0x9F, 0x1C, 0x12, 0x33, 0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32, 0x30, 0x34, 0x30, 0x34, |
04 |
0x32, 0x37, 0x31, 0x38, 0x9F, 0x62, 0x01, 0x01, 0x57, 0x12, 0x62, 0x22, 0x89, 0x00, 0x00, 0x02, 0x91, |
05 |
0x01, 0xD0, 0x90, 0x32, 0x01, 0x02, 0x47, 0x17, 0x13, 0x00, 0x0F, 0x5F, 0x20, 0x0A, 0x48, 0x55, 0x47, |
06 |
0x55, 0x4F, 0x20, 0x4D, 0x49, 0x4E, 0x47, 0x9F, 0x1F, 0x3C, 0x25, 0x39, 0x39, 0x36, 0x32, 0x32, 0x32, |
07 |
0x38, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x39, 0x31, 0x30, 0x31, 0x5E, 0x47, 0x55, 0x4F, 0x20, |
08 |
0x4D, 0x49, 0x4E, 0x47, 0x2F, 0x48, 0x55, 0x5E, 0x30, 0x39, 0x30, 0x33, 0x32, 0x30, 0x31, 0x30, 0x32, |
09 |
0x34, 0x37, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x38, 0x39, 0x30, |
13 |
TLVEntity tlvEntity[TLV_MAX_LENGTH]; |
14 |
unsigned int tlv_count; |
16 |
TLVPackage::Construct(requestBuf, sizeof (requestBuf), tlvEntity, tlv_count); |
18 |
unsigned char parseBuf[1024]; |
19 |
unsigned int buf_count; |
21 |
TLVPackage::Parse(tlvEntity, tlv_count, parseBuf, buf_count); |
23 |
if ( strncmp (( char *)parseBuf, ( char *)requestBuf, sizeof (requestBuf)) == 0) |
25 |
AfxMessageBox( "TRUE" ); |
29 |
AfxMessageBox( "FALSE" ); |
最后测试结果中,可以得到最后将弹出“TRUE”的对话框。证明构造TLV得到的TLVEntity,再对这个实体进行解析,可以的到解析后的字节数组,最后通过strncmp的方法比较判断,是否原始字节数组和解析后的字节数组是否一致。
另外,为了方便C#程序员的使用,我对该C++程序重新改写了一下,得出的结果也是一样的。
TLVEntity.cs
04 |
public class TLVEntity |
09 |
public byte [] Tag { get ; set ; } |
14 |
public byte [] Length { get ; set ; } |
19 |
public byte [] Value { get ; set ; } |
24 |
public int TagSize { get ; set ; } |
29 |
public int LengthSize { get ; set ; } |
34 |
public TLVEntity Sub_TLVEntity { get ; set ; } |
以及TLVPackage.cs
004 |
public class TLVPackage |
009 |
/// <param name="buffer"> |
010 |
public static List<tlventity> Construct( byte [] buffer) |
012 |
List<tlventity> list = new List<tlventity>(); |
013 |
int currentTLVIndex = 0; |
014 |
int currentIndex = 0; |
015 |
int currentStatus = 'T' ; |
018 |
TLVEntity tlvEntity = null ; |
020 |
while (currentIndex < buffer.Length) |
022 |
switch (currentStatus) |
025 |
tlvEntity = new TLVEntity(); |
028 |
if ((buffer[currentIndex] & 0x20) != 0x20) |
030 |
tlvEntity.Sub_TLVEntity = null ; |
032 |
if ((buffer[currentIndex] & 0x1f) == 0x1f) |
034 |
int endTagIndex = currentIndex; |
035 |
while ((buffer[++endTagIndex] & 0x80) == 0x80) ; |
036 |
int tagSize = endTagIndex - currentIndex + 1; |
038 |
tlvEntity.Tag = new byte [tagSize]; |
039 |
Array.Copy(buffer, currentIndex, tlvEntity.Tag, 0, tagSize); |
041 |
tlvEntity.TagSize = tagSize; |
043 |
currentIndex += tagSize; |
047 |
tlvEntity.Tag = new byte [1]; |
048 |
Array.Copy(buffer, currentIndex, tlvEntity.Tag, 0, 1); |
050 |
tlvEntity.TagSize = 1; |
058 |
if ((buffer[currentIndex] & 0x1f) == 0x1f) |
060 |
int endTagIndex = currentIndex; |
061 |
while ((buffer[++endTagIndex] & 0x80) == 0x80) ; |
062 |
int tagSize = endTagIndex - currentIndex + 1; |
064 |
tlvEntity.Tag = new byte [tagSize]; |
065 |
Array.Copy(buffer, currentIndex, tlvEntity.Tag, 0, tagSize); |
067 |
tlvEntity.TagSize = tagSize; |
069 |
currentIndex += tagSize; |
073 |
tlvEntity.Tag = new byte [1]; |
074 |
Array.Copy(buffer, currentIndex, tlvEntity.Tag, 0, 1); |
076 |
tlvEntity.TagSize = 1; |
085 |
if ((buffer[currentIndex] & 0x80) == 0x80) |
087 |
for ( int index = 0; index < 2; index++) |
089 |
subLength += buffer[currentIndex + 1 + index] << (index * 8); |
092 |
temp = new byte [subLength]; |
094 |
Array.Copy(buffer, currentIndex + 3, temp, 0, subLength); |
098 |
subLength = buffer[currentIndex]; |
100 |
temp = new byte [subLength]; |
102 |
Array.Copy(buffer, currentIndex + 1, temp, 0, subLength); |
106 |
tlvEntity.Sub_TLVEntity = new TLVEntity(); |
107 |
List<tlventity> tempList = Construct(temp); |
108 |
tlvEntity.Sub_TLVEntity = tempList[0]; |
115 |
if ((buffer[currentIndex] & 0x80) != 0x80) |
117 |
tlvEntity.Length = new byte [1]; |
118 |
Array.Copy(buffer, currentIndex, tlvEntity.Length, 0, 1); |
120 |
tlvEntity.LengthSize = 1; |
122 |
valueSize = tlvEntity.Length[0]; |
129 |
int lengthSize = buffer[currentIndex] & 0x7f; |
133 |
for ( int index = 0; index < lengthSize; index++) |
135 |
valueSize += buffer[currentIndex + index] << (index * 8); |
138 |
tlvEntity.Length = new byte [lengthSize]; |
139 |
Array.Copy(buffer, currentIndex, tlvEntity.Length, 0, lengthSize); |
141 |
tlvEntity.LengthSize = lengthSize; |
143 |
currentIndex += lengthSize; |
149 |
tlvEntity.Value = new byte [valueSize]; |
150 |
Array.Copy(buffer, currentIndex, tlvEntity.Value, 0, valueSize); |
152 |
currentIndex += valueSize; |
160 |
return new List<tlventity>(); |
170 |
/// <param name="list"> |
171 |
/// <returns></returns> |
172 |
public static byte [] Parse(List<tlventity> list) |
174 |
byte [] buffer = new byte [4096]; |
175 |
int currentIndex = 0; |
176 |
int currentTLVIndex = 0; |
179 |
while (currentTLVIndex < list.Count()) |
182 |
TLVEntity entity = list[currentTLVIndex]; |
184 |
Array.Copy(entity.Tag, 0, buffer, currentIndex, entity.TagSize); |
186 |
currentIndex += entity.TagSize; |
188 |
for ( int index = 0; index < entity.LengthSize; index++) |
190 |
valueSize += entity.Length[index] << (index * 8); |
194 |
buffer[currentIndex] = Convert.ToByte(0x80 | entity.LengthSize); |
198 |
Array.Copy(entity.Length, 0, buffer, currentIndex, entity.LengthSize); |
200 |
currentIndex += entity.LengthSize; |
202 |
if (entity.Sub_TLVEntity == null ) |
204 |
Array.Copy(entity.Value, 0, buffer, currentIndex, valueSize); |
205 |
currentIndex += valueSize; |
209 |
byte [] tempBuffer = Parse( new List<tlventity> { entity.Sub_TLVEntity }); |
210 |
Array.Copy(tempBuffer, 0, buffer, currentIndex, tempBuffer.Length); |
211 |
currentIndex += tempBuffer.Length; |
217 |
byte [] resultBuffer = new byte [currentIndex]; |
218 |
Array.Copy(buffer, 0, resultBuffer, 0, currentIndex); |
222 |
}</tlventity></tlventity></tlventity></tlventity></tlventity></tlventity></tlventity> |
接着,写测试程序:
01 |
static byte [] requestBuffer = |
03 |
0x9F, 0x1C, 0x82, 0x2C, 0x01, |
04 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
05 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
06 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
07 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
08 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
09 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
10 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
11 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
12 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
13 |
0x33,
0x33, 0x30, 0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30,
0x32, 0x32, 0x37, 0x31, 0x39, 0x36, 0x32,0x33, 0x33, 0x30, 0x32, 0x32,
0x37, 0x31, 0x39, 0x36, 0x32, |
14 |
0x71, 0x07, 0x9f, 0x19, 0x04, 0x11, 0x22, 0x33, 0x44 |
17 |
static void Main( string [] args) |
19 |
Console.WriteLine( "待组装的数据包:" ); |
20 |
requestBuffer.ToList().ForEach(o => { Console.Write( "{0}," , "0x" + o.ToString( "X" )); }); |
22 |
Console.WriteLine( "\r\n\r\n开始构造TLV" ); |
24 |
List<tlventity> list = TLVPackage.Construct(requestBuffer); |
25 |
Console.WriteLine( "\r\n构造结束!" ); |
27 |
Console.WriteLine( "\r\n开始解析TLV" ); |
29 |
byte [] responseBuffer = TLVPackage.Parse(list); |
30 |
Console.WriteLine( "\r\n解析结束!" ); |
32 |
Console.WriteLine( "\r\n解析结果:" ); |
34 |
if (ByteEquals(requestBuffer, responseBuffer)) |
36 |
Console.WriteLine( "Equal!" ); |
40 |
Console.WriteLine( "Not Equal!" ); |
运行结果:
总结
TLV在数据通信方面,其实运用得很广泛,在应用层数据通信中,如HTTP协议,HTML,XML语言本身定义了一些标签
(Body,Head,Script)对数据串行化,接收方再根据标签解析原始数据,通过浏览器展现出来。因此本质上也是属于TLV协议的设计模式。甚至
在传输层的TCP,UDP协议,你完全可以通过TLV实现自定义的应用协议。
附上C#源代码:TLVPackageDemo.rar