什麼是TLV?
哈哈哈哈哈哈,碰見新東西,先來一手我的學習三大問:這是什麼?有什麼用?怎麼做?
先了解下TLV——BER編碼的一種,ASN1标準,由Tag(标簽),Length(長度),Value(值)而來。what?我一看,前面兩個又沒聽過,怎嘛辦,上百度查了查,這麼高大的出身,我這代碼寫的都覺得對不起這個它。
ASN.1抽象文法标記(Abstract Syntax Notation One) ASN.1是一種 ISO/ITU-T 标準,描述了一種對資料進行表示、編碼、傳輸和解碼的資料格式。它提供了一整套正規的格式用于描述對象的結構,而不管語言上如何執行及這些資料的具體指代,也不用去管到底是什麼樣的應用程式。ASN.1 取得成功的一個主要原因是它與幾個标準化編碼規則相關,如基本編碼規則(BER) -X.209 、規範編碼規則(CER)、識别名編碼規則(DER)、壓縮編碼規則(PER)和 XML編碼規則(XER)。1984年,ASN.1 就已經成為了一種國際标準。
TLV的資料封裝
它是一種按照tag,length,value這樣的順序來對資料進行封裝的規則,在記憶體中如下圖表示:

比如我們将要發送的資料是:0x03 0x06 0x32 0x31 0x32 0x35
按照TLV的定義:
其中0x03表示标簽,表示這是哪一類的資料,不然你收到這樣的資料是什麼樣的。
0x06表示這段資料總共有6個位元組,包括tag和length。
後面的四個位元組表示值。這樣叫做一幀資料,假如收到了許多幀,那麼僅僅依靠TAG來判斷資料是會出問題的。比如:
0x03 0x03 0x32 0x03 0x03 0x03 0x03 哦豁,傻眼
這樣就需要定義一個一幀資料的開始,HEAD,就變成了這樣:
一幀資料的第一個位元組定義為頭,一般設為資料中最不可能出現的值,比如0xff。但是這樣還是不好,萬一我非要發0xff怎麼辦對吧,我就要搞它。不光是這樣,我們封裝好的資料再發送的過程中是通過實體層的電纜傳輸出去的,那麼也就難免會出錯(可能有人會說,在信道傳輸過程中本身就有糾錯機制,都是機率問題,萬一!!對吧)。比如後面的值由0x35變成0x31,對于一般的資料來說沒什麼影響,但假如這個值控制着某個警報,遇險情卻沒有報警。那就GG。有沒什麼方法可以對資料進行檢錯呢?確定自己收到的是正确的資料。對于這種糾錯的暫時知道的奇偶校驗和CRC校驗。奇偶校驗就是判斷資料中0或1的的個數,因為2個位同時出錯的機率很低。關于CRC校驗可參考https://blog.csdn.net/xing414736597/article/details/78693781
CRC算法可以通過資料算出一個CRC值,發送資料的一方可以先算出這個值,然後封裝到這一幀資料的尾部,在資料的接受端采用同樣的算法算出這個值再和資料尾部的這個值作比較,若果相等,則正确。CRC的實作可參考我代碼中的兩個檔案,實作别人代碼的最大複用,哈哈哈哈哈 “crc-itu-t.h”,“crc-itu-t.c” GitHub位址
這裡用到了CRC-16,也就是兩個位元組,那麼最後就可以表示成下面這樣:
最終一幀資料占用九個位元組。
資料的封包就可以按照這個這個順序來組織,不過我看大多是都是用的結構體實作,我這裡沒有用到結構體就是一個簡單的實作。
#include "crc-itu-t.h"
#include "crc-itu-t.c"
/* TLV Packet format:
*
*+------------+---------+------------+-----------+-----------+
*| Header(1B) | Tag(1B) | Length(1B) | Value(4B) | CRC16(2B) |
*+------------+---------+------------+-----------+-----------+
*/
#define TLV_FIXED_SIZE 5 // ex Value
#define TLV_SIZE 8
#define BUFSIZE 64
#define PACK_HEADER 0xff
/*define tag, enum Automatically add 1*/
enum{
NAME = 1,
DEVICE,
TEMPRATURE,
HUMIDITY,
};
int packtlv(char *buf,int bufsize,char *data,int datalen int packtype)
{
int offset = 0;
unsigned short crc16 = 0;
if((!buf) || (!data) || (bufsize < TLV_SIZE))
{
printf("the function packtlv_tem() with error parameters\n");
return;
}
/***head***/
buf[offset] = PACK_HEADER;
offset+=1;
/***tag***/
buf[offset] = packtype;
offset+=1;
if(datalen > (bufsize-TLV_FIXED_SIZE))
{
printf("the data is too long,please change the size and enter again\n");
}
/***length***/
buf[offset] = TLV_FIXED_SIZE+datalen;
offset+=1;
/***value***/
int i = 0;
for(i;i<datalen;i++)
{
buf[offset++] = data[i];
}
/***crc***/
/*Calculate the CRC value*/
crc16 = crc_itu_t(MAGIC_CRC, buf, offset);
/*Adds a two-byte CRC value to packet buf*/
ushort_to_bytes(&buf[offset], crc16);
offset += 2;
return offset;
}
TLV的資料解析
總感覺這裡面有bug,希望可以有大佬指出更好的實作方式,一定虛心求教。
char buf[64];
memset(buf,0,sizeof(buf));
int remain = 0;
int rv = -1;
while(1)
{
printf("waitting for data coming...\n");
rv = read(new_fd,&buf[remain],sizeof(buf)-remain);
if(rv < 0)
{
printf("read failure;%s\n",strerror(errno));
close(new_fd);
break;
}
else if(rv == 0)
{
printf("the client has disconnected\n");
close(new_fd);
break;
}
remain+=rv;
printf("rv = %d\n",rv);
printf("before unpacktlv remain = %d\n",remain);
printf("bufsize = %d\n",(int)sizeof(buf));
remain = unpacktlv(buf,sizeof(buf),rv,remain);
if(remain < 0)
{
return;
}
123,3-17 46%
return;
}
printf("There are the contents of the buffer\n");
printf("**************************\n");
int i = 1;
for(i;i<sizeof(buf)+1;i++)
{
printf("0x%02x ",(unsigned char)buf[i-1]);
if((i%16) == 0)
{
printf("\n");
}
}
printf("\n**************************\n");
}
int unpacktlv(char *buf,int bufsize,int readret,int remain)
{
/***longth can not parse***/
if(remain < (TLV_FIXED_SIZE+1))
{
printf("the data is too little that Can't parse data\n");
//*remain = readret;
printf("remain = %d\n",remain);
return remain;
}
/***begain to look head***/
int current;
fdhdfr0:
current = 0;
for(current;current<remain;current++)
{
/***current is head***/
if((unsigned char)buf[current] == PACK_HEADER)
{
printf("current = %d\n",current);
/***fond head but data is incomplete compare with smallest possible***/
if((remain-current)<(TLV_FIXED_SIZE+1))
{
printf("1:This data is incomplete\n");
memmove(buf,&buf[current],remain-current);
remain = remain-current;
printf("remain = %d\n",remain);
return remain;
}
int help = current;
help+=2;
/***data is incomplete***/
if(((buf[help]-TLV_FIXED_SIZE) > MAXDATALEN) || (buf[help]<1))
{
printf("this is not the true HEAD or Data length may be wrong\n");
continue;
}
else if((buf[help]>(remain-current)))
{
printf("2:This data is incomplete\n");
memmove(buf,&buf[current],remain-current);
remain = remain-current;
printf("remain = %d\n",remain);
return remain;
}
/***The length of the right,begain to calculate CRC***/
int datalen = 0;
datalen = (unsigned char)buf[help];//取值時一定要轉換成無符号類型
unsigned short crc16;
help = help+datalen-TLV_FIXED_SIZE+1;
printf("help = %d\n",help);
crc16 = crc_itu_t(MAGIC_CRC,&buf[current],help-current);
printf("crc16 = %d\n",crc16);
/***crc right***/
if(crc16 == bytes_to_ushort(&buf[help],2))
{
//char data[bufsize];
printf("this is the right data\n");
if(buf[current+1] == TEMPRATURE)
{
int i = 0;
for(i;i<datalen;i++)
{
printf("0x%02x ",(unsigned char)buf[current]);
current++;
}
printf("\n");
memmove(buf,&buf[current],remain-current);
remain = remain-current;
printf("remain = %d\n",remain);
goto fdhdfr0;
}
else
{
printf("the tag not define\n");
int i = 0;
for(i;i<datalen;i++)
{
printf("0x%02x ",(unsigned char)buf[current]);
}
printf("\n");
memmove(buf,&buf[current],remain-current);
remain = remain-current;
printf("remain = %d\n",remain);
goto fdhdfr0;
}
}
else
{
printf("Not the right head or data wrong!\n");
continue;
}
}
}
printf("Find the end but can not look for other head from buf\n");
remain = 0;
return remain;
}
正确資料0xff 0x03 0x09 0x32 0x31 0x32 0x35 0x80 0x9f 自己測了一下暫時還沒發現啥