-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #177 from aceld/add_htlvcrc
Add htlvcrc
- Loading branch information
Showing
31 changed files
with
1,900 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
# LengthFieldFrameDecoder使用详解 | ||
|
||
|
||
>LengthFieldFrameDecoder是一个基于长度字段的解码器,比较难理解的解码器,它主要有5个核心的参数配置: | ||
>maxFrameLength: 数据包最大长度 | ||
>lengthFieldOffset: 长度字段偏移量 | ||
>lengthFieldLength: 长度字段所占的字节数 | ||
>lengthAdjustment: 长度的调整值 | ||
>initialBytesToStrip:解码后跳过的字节数 | ||
|
||
|
||
|
||
## 示例讲解 | ||
|
||
#### TLV格式协议 | ||
|
||
TLV,即Tag(Type)—Length—Value,是一种简单实用的数据传输方案。在TLV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)。这里的长度域的值实际上就是内容域的长度。 | ||
|
||
``` | ||
解码前 (20 bytes) 解码后 (20 bytes) | ||
+------------+------------+-----------------+ +------------+------------+-----------------+ | ||
| Tag | Length | Value |----->| Tag | Length | Value | | ||
| 0x00000001 | 0x0000000C | "HELLO, WORLD" | | 0x00000001 | 0x0000000C | "HELLO, WORLD" | | ||
+------------+------------+-----------------+ +------------+------------+-----------------+ | ||
``` | ||
> Tag: uint32类型,占4字节,Tag作为MsgId,暂定为1<br> | ||
> Length:uint32类型,占4字节,Length标记Value长度12(hex:0x0000000C)<br> | ||
> Value: 共12个字符,占12字节<br> | ||
``` | ||
说明: | ||
lengthFieldOffset = 4 (Length的字节位索引下标是4) 长度字段的偏差 | ||
lengthFieldLength = 4 (Length是4个byte) 长度字段占的字节数 | ||
lengthAdjustment = 0 (Length只表示Value长度,程序只会读取Length个字节就结束,后面没有来,故为0,若Value后面还有crc占2字节的话,那么此处就是2。若Length标记的是Tag+Length+Value总长度,那么此处是-8) | ||
initialBytesToStrip = 0 (这个0表示返回完整的协议内容Tag+Length+Value,如果只想返回Value内容,去掉Tag的4字节和Length的4字节,此处就是8) 从解码帧中第一次去除的字节数 | ||
maxFrameLength = 2^32 + 4 + 4 (Length为uint类型,故2^32次方表示Value最大长度,此外Tag和Length各占4字节) | ||
``` | ||
|
||
|
||
#### HTLV+CRC格式协议 | ||
|
||
HTLV+CRC,H头码,T功能码,L数据长度,V数据内容 | ||
|
||
|
||
``` | ||
+------+-------+---------+--------+--------+ | ||
| 头码 | 功能码 | 数据长度 | 数据内容 | CRC校验 | | ||
| 1字节 | 1字节 | 1字节 | N字节 | 2字节 | | ||
+------+-------+---------+--------+--------+ | ||
``` | ||
|
||
数据示例 | ||
|
||
``` | ||
头码 功能码 数据长度 Body CRC | ||
A2 10 0E 0102030405060708091011121314 050B | ||
``` | ||
|
||
``` | ||
说明: | ||
1.数据长度len是14(0E),这里的len仅仅指Body长度; | ||
lengthFieldOffset = 2 (len的索引下标是2,下标从0开始) 长度字段的偏差 | ||
lengthFieldLength = 1 (len是1个byte) 长度字段占的字节数 | ||
lengthAdjustment = 2 (len只表示Body长度,程序只会读取len个字节就结束,但是CRC还有2byte没读呢,所以为2) | ||
initialBytesToStrip = 0 (这个0表示完整的协议内容,如果不想要A2,那么这里就是1) 从解码帧中第一次去除的字节数 | ||
maxFrameLength = 255 + 4(起始码、功能码、CRC) (len是1个byte,所以最大长度是无符号1个byte的最大值) | ||
``` | ||
|
||
|
||
## 案例分析 | ||
以下7种案例足以满足所有协议,只处理断粘包,并不能处理错包,包的完整性需要依靠协议自身定义CRC来校验 | ||
|
||
#### 案例1: | ||
``` | ||
lengthFieldOffset =0 长度字段从0开始 | ||
lengthFieldLength =2 长度字段本身占2个字节 | ||
lengthAdjustment =0 需要调整0字节 | ||
initialBytesToStrip=0 解码后跳过0字节 | ||
解码前 (14 bytes) 解码后 (14 bytes) | ||
+--------+----------------+ +--------+----------------+ | ||
| Length | Actual Content |----->| Length | Actual Content | | ||
| 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" | | ||
+--------+----------------+ +--------+----------------+ | ||
``` | ||
|
||
> Length为0x000C,这个是十六进制,0x000C转化十进制就是14 | ||
|
||
#### 案例2: | ||
``` | ||
lengthFieldOffset =0 长度字段从0开始 | ||
lengthFieldLength =2 长度字段本身占2个字节 | ||
lengthAdjustment =0 需要调整0字节 | ||
initialBytesToStrip=2 解码后跳过2字节 | ||
解码前 (14 bytes) 解码后 (12 bytes) | ||
+--------+----------------+ +----------------+ | ||
| Length | Actual Content |----->| Actual Content | | ||
| 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" | | ||
+--------+----------------+ +----------------+ | ||
``` | ||
>这时initialBytesToStrip字段起作用了,在解码后会将前面的2字节跳过,所以解码后就只剩余了数据部分。 | ||
#### 案例3: | ||
``` | ||
lengthFieldOffset =0 长度字段从0开始 | ||
lengthFieldLength =2 长度字段本身占2个字节 | ||
lengthAdjustment =-2 需要调整 -2 字节 | ||
initialBytesToStrip=0 解码后跳过2字节 | ||
解码前 (14 bytes) 解码后 (14 bytes) | ||
+--------+----------------+ +--------+----------------+ | ||
| Length | Actual Content |----->| Length | Actual Content | | ||
| 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" | | ||
+--------+----------------+ +--------+----------------+ | ||
``` | ||
|
||
>这时lengthAdjustment起作用了,因为长度字段的值包含了长度字段本身的2字节, | ||
如果要获取数据的字节数,需要加上lengthAdjustment的值,就是 14+(-2)=12,这样才算出来数据的长度。 | ||
|
||
|
||
#### 案例4: | ||
|
||
``` | ||
lengthFieldOffset =2 长度字段从第2个字节开始 | ||
lengthFieldLength =3 长度字段本身占3个字节 | ||
lengthAdjustment =0 需要调整0字节 | ||
initialBytesToStrip=0 解码后跳过0字节 | ||
解码前 (17 bytes) 解码后 (17 bytes) | ||
+----------+----------+----------------+ +----------+----------+----------------+ | ||
| Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content | | ||
| 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" | | ||
+----------+----------+----------------+ +----------+----------+----------------+ | ||
``` | ||
>由于数据包最前面加了2个字节的Header,所以lengthFieldOffset为2, | ||
说明长度字段是从第2个字节开始的。然后lengthFieldLength为3,说明长度字段本身占了3个字节。 | ||
|
||
|
||
#### 案例5: | ||
``` | ||
lengthFieldOffset =0 长度字段从第0个字节开始 | ||
lengthFieldLength =3 长度字段本身占3个字节 | ||
lengthAdjustment =2 需要调整2字节 | ||
initialBytesToStrip=0 解码后跳过0字节 | ||
解码前 (17 bytes) 解码后 (17 bytes) | ||
+----------+----------+----------------+ +----------+----------+----------------+ | ||
| Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content | | ||
| 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" | | ||
+----------+----------+----------------+ +----------+----------+----------------+ | ||
``` | ||
>lengthFieldOffset为0,所以长度字段从0字节开始。lengthFieldLength为3,长度总共占3字节。 | ||
因为长度字段后面还剩余14字节的总数据,但是长度字段的值为12,只表示了数据的长度,不包含头的长度, | ||
所以lengthAdjustment为2,就是12+2=14,计算出Header+Content的总长度。 | ||
|
||
|
||
#### 案例6: | ||
|
||
``` | ||
lengthFieldOffset =1 长度字段从第1个字节开始 | ||
lengthFieldLength =2 长度字段本身占2个字节 | ||
lengthAdjustment =1 需要调整1字节 | ||
initialBytesToStrip=3 解码后跳过3字节 | ||
解码前 (16 bytes) 解码后 (13 bytes) | ||
+------+--------+------+----------------+ +------+----------------+ | ||
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | | ||
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | | ||
+------+--------+------+----------------+ +------+----------------+ | ||
``` | ||
>这一次将Header分为了两个1字节的部分,lengthFieldOffset为1表示长度从第1个字节开始,lengthFieldLength为2表示长度字段占2个字节。 | ||
因为长度字段的值为12,只表示了数据的长度,所以lengthAdjustment为1,12+1=13, | ||
表示Header的第二部分加上数据的总长度为13。因为initialBytesToStrip为3,所以解码后跳过前3个字节。 | ||
|
||
|
||
#### 案例7: | ||
``` | ||
lengthFieldOffset =1 长度字段从第1个字节开始 | ||
lengthFieldLength =2 长度字段本身占2个字节 | ||
lengthAdjustment =-3 需要调整 -3 字节 | ||
initialBytesToStrip=3 解码后跳过3字节 | ||
解码前 (16 bytes) 解码后 (13 bytes) | ||
+------+--------+------+----------------+ +------+----------------+ | ||
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | | ||
| 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | | ||
+------+--------+------+----------------+ +------+----------------+ | ||
``` | ||
>这一次长度字段的值为16,表示包的总长度,所以lengthAdjustment为 -3 ,16+ (-3)=13, | ||
表示Header的第二部分加数据部分的总长度为13字节。initialBytesToStrip为3,解码后跳过前3个字节。 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# 一款水机设备服务端模拟程序 | ||
|
||
|
||
```azure | ||
HTLV+CRC,H头码,T功能码,L数据长度,V数据内容 | ||
+------+-------+---------+--------+--------+ | ||
| 头码 | 功能码 | 数据长度 | 数据内容 | CRC校验 | | ||
| 1字节 | 1字节 | 1字节 | N字节 | 2字节 | | ||
+------+-------+---------+--------+--------+ | ||
头码 功能码 数据长度 Body CRC | ||
A2 10 0E 0102030405060708091011121314 050B | ||
说明: | ||
1.数据长度len是14(0E),这里的len仅仅指Body长度; | ||
lengthFieldOffset = 2 (len的索引下标是2,下标从0开始) 长度字段的偏差 | ||
lengthFieldLength = 1 (len是1个byte) 长度字段占的字节数 | ||
lengthAdjustment = 2 (len只表示Body长度,程序只会读取len个字节就结束,但是CRC还有2byte没读呢,所以为2) | ||
initialBytesToStrip = 0 (这个0表示完整的协议内容,如果不想要A2,那么这里就是1) 从解码帧中第一次去除的字节数 | ||
maxFrameLength = 255 + 4(起始码、功能码、CRC) (len是1个byte,所以最大长度是无符号1个byte的最大值) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/aceld/zinx/examples/zinx_decoder/bili/router" | ||
"github.com/aceld/zinx/examples/zinx_decoder/decode" | ||
"github.com/aceld/zinx/ziface" | ||
"github.com/aceld/zinx/znet" | ||
"math" | ||
) | ||
|
||
func DoConnectionBegin(conn ziface.IConnection) { | ||
} | ||
|
||
func DoConnectionLost(conn ziface.IConnection) { | ||
} | ||
|
||
func main() { | ||
server := znet.NewServer(func(s *znet.Server) { | ||
s.Port = 9090 | ||
s.LengthField = ziface.LengthField{ | ||
MaxFrameLength: math.MaxUint8 + 4, | ||
LengthFieldOffset: 2, | ||
LengthFieldLength: 1, | ||
LengthAdjustment: 2, | ||
InitialBytesToStrip: 0, | ||
} | ||
}) | ||
server.SetOnConnStart(DoConnectionBegin) | ||
server.SetOnConnStop(DoConnectionLost) | ||
server.AddInterceptor(&decode.HtlvCrcDecoder{}) | ||
server.AddRouter(0x10, &router.Data0x10Router{}) | ||
server.AddRouter(0x13, &router.Data0x13Router{}) | ||
server.AddRouter(0x14, &router.Data0x14Router{}) | ||
server.AddRouter(0x15, &router.Data0x15Router{}) | ||
server.AddRouter(0x16, &router.Data0x16Router{}) | ||
server.Serve() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package router | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"github.com/aceld/zinx/examples/zinx_decoder/bili/utils" | ||
"github.com/aceld/zinx/examples/zinx_decoder/decode" | ||
"github.com/aceld/zinx/ziface" | ||
"github.com/aceld/zinx/zlog" | ||
"github.com/aceld/zinx/znet" | ||
) | ||
|
||
type Data0x10Router struct { | ||
znet.BaseRouter | ||
} | ||
|
||
func (this *Data0x10Router) Handle(request ziface.IRequest) { | ||
zlog.Ins().DebugF("Data0x10Router Handle %s \n", hex.EncodeToString(request.GetMessage().GetData())) | ||
_response := request.GetResponse() | ||
if _response != nil { | ||
switch _response.(type) { | ||
case decode.HtlvCrcData: | ||
_data := _response.(decode.HtlvCrcData) | ||
//zlog.Ins().DebugF("Data0x10Router %v \n", _data) | ||
buffer := pack10(_data) | ||
request.GetConnection().Send(buffer) | ||
} | ||
} | ||
} | ||
|
||
// 头码 功能码 数据长度 Body CRC | ||
// A2 10 0E 0102030405060708091011121314 050B | ||
func pack10(_data decode.HtlvCrcData) []byte { | ||
buffer := bytes.NewBuffer([]byte{}) | ||
buffer.WriteByte(0xA1) | ||
buffer.WriteByte(_data.Funcode) | ||
buffer.WriteByte(0x1E) | ||
//3~9:唯一设备码 将IMEI码转换为16进制 | ||
buffer.Write(_data.Body[:7]) | ||
//10~14:园区代码 后台根据幼儿园生成的唯一代码 | ||
buffer.Write([]byte{10, 11, 12, 13, 14}) | ||
//15~18:时间戳 实际当前北京时间的时间戳,转换为16进制 | ||
buffer.Write([]byte{15, 16, 17, 18}) | ||
//19:RFID模块工作模式 0x01-离线工作模式(默认工作模式)0x02-在线工作模式 | ||
buffer.WriteByte(0x02) | ||
//20~27:通讯密匙 预留,全填0x00 | ||
buffer.Write([]byte{20, 21, 22, 23, 24, 25, 26, 27}) | ||
//28:出水方式 0x00-放杯出水,取杯停止出水 0x01-刷一下卡出水,再刷停止出水【数联默认】 | ||
buffer.WriteByte(0x01) | ||
//29~32:预留 全填0x00 | ||
buffer.Write([]byte{29, 30, 31, 32}) | ||
crc := utils.GetCrC(buffer.Bytes()) | ||
buffer.Write(crc) | ||
return buffer.Bytes() | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package router | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"github.com/aceld/zinx/examples/zinx_decoder/bili/utils" | ||
"github.com/aceld/zinx/examples/zinx_decoder/decode" | ||
"github.com/aceld/zinx/ziface" | ||
"github.com/aceld/zinx/znet" | ||
) | ||
|
||
type Data0x13Router struct { | ||
znet.BaseRouter | ||
} | ||
|
||
func (this *Data0x13Router) Handle(request ziface.IRequest) { | ||
fmt.Println("Data0x13Router Handle", request.GetMessage().GetData()) | ||
_response := request.GetResponse() | ||
if _response != nil { | ||
switch _response.(type) { | ||
case decode.HtlvCrcData: | ||
_data := _response.(decode.HtlvCrcData) | ||
fmt.Println("Data0x13Router", _data) | ||
buffer := pack13(_data) | ||
request.GetConnection().Send(buffer) | ||
} | ||
} | ||
} | ||
|
||
// 头码 功能码 数据长度 Body CRC | ||
// A2 10 0E 0102030405060708091011121314 050B | ||
func pack13(_data decode.HtlvCrcData) []byte { | ||
buffer := bytes.NewBuffer([]byte{}) | ||
buffer.WriteByte(0xA1) | ||
buffer.WriteByte(_data.Funcode) | ||
buffer.WriteByte(0x0E) | ||
//3~9:3~6:用户卡号 用户IC卡卡号 | ||
buffer.Write(_data.Body[:4]) | ||
//7:卡状态: 0x00-未绑定(如服务器未查询到该IC卡时) | ||
//0x01-已绑定 | ||
//0x02-解除绑定(如服务器查询到该IC卡解除绑定时下发) | ||
buffer.WriteByte(0x01) | ||
//8~9:剩余使用天数 该用户的剩余流量天数 | ||
buffer.Write([]byte{8, 9}) | ||
//10~11:每次最大出水量 单位mL,实际出水量 | ||
buffer.Write([]byte{10, 11}) | ||
//12~16:预留 全填0x00 | ||
buffer.Write([]byte{12, 13, 14, 15, 16}) | ||
crc := utils.GetCrC(buffer.Bytes()) | ||
buffer.Write(crc) | ||
return buffer.Bytes() | ||
|
||
} |
Oops, something went wrong.