12、从0到1实现SECS协议之SECS-I协议编码与解码
1、SECS-I 协议编码与解码实现
这个感觉没有啥特别好说的,根据协议慢慢理解就好了,代码实现如下:
package packets
import (
"encoding/binary"
"fmt"
)
/*-------------------------------------
secs头
-------------------------------------*/
type SecsHeader struct {
// Header的第一字节中的第一个比特: R-bit,其作用是指出消息传输的方向。
// IsToHost=true --> equipment -> host
// IsToHost=false --> equipment <- host
IsToHost bool
// Header的第一和第二字节, 标识一个通信设备
SessionID uint16
// Header的第三字节中的第一个比特: Wait-bit,用于指示消息的发送者是否需要回复。
RequireResponse bool
// 第一位用于标识 R-bit
Stream uint8
Function uint8
// End-bit, 用于标识是否是最后一个块
IsLastBlock bool
// 第一个比特为 End-bit
// 0: 还有数据传输
// 1: 最后一个块
BlockNumber uint16
// Header的最后四个字节
// 第七和第八字节表示 source ID,用于表示消息的发送者,
// 第九和第十字节表示 transaction ID,用于唯一标示每个发送的消息。
// 但是代码中实现并没有按照这种格式来?
System uint32
// 一个两个字节校验和
CheckSum uint16
// 校验是否成功
CheckSumPassed bool
}
// 方便打印结构体时,以人性化的方式显示
func (sh SecsHeader) String() string {
return fmt.Sprintf(
"{SessionID:%d, Stream:%d, Function:%d, isLastBlock:%v, BlockNumber:%d,"+
" System:0x%x, RequireResponse:%v, IsToHost:%v}",
sh.SessionID,
sh.Stream,
sh.Function,
sh.IsLastBlock,
sh.BlockNumber,
sh.System,
sh.RequireResponse,
sh.IsToHost)
}
// Encode SecsHeader 的编码方式
func (sh *SecsHeader) Encode() []byte {
var result []byte
//session id
var sessionIdBytes = make([]byte, 2)
if sh.IsToHost {
// Header的第一字节中的第一个比特: R-bit,其作用是指出消息传输的方向。
binary.BigEndian.PutUint16(sessionIdBytes, sh.SessionID|0b1000000000000000)
} else {
// 这里语义清晰可以这样写,不过 0 或 x(任何数) 的结果都是 x
//binary.BigEndian.PutUint16(sessionIdBytes, sh.SessionID|0b0000000000000000)
binary.BigEndian.PutUint16(sessionIdBytes, sh.SessionID)
}
result = append(result, sessionIdBytes...)
//stream
if sh.RequireResponse {
// Header的第三字节中的第一个比特: Wait-bit,用于指示消息的发送者是否需要回复。
// 1 表示需要 回复
result = append(result, sh.Stream|0b10000000)
} else {
result = append(result, sh.Stream)
}
//function
result = append(result, sh.Function)
//block number
var blockNumberBytes = make([]byte, 2)
if sh.IsLastBlock {
// End-bit, 用于标识是否是最后一个块
// 1 表示最后一个块
binary.BigEndian.PutUint16(blockNumberBytes, sh.BlockNumber|0b1000000000000000)
} else {
binary.BigEndian.PutUint16(blockNumberBytes, sh.BlockNumber)
}
result = append(result, blockNumberBytes...)
//system
var systemBytes = make([]byte, 4)
binary.BigEndian.PutUint32(systemBytes, sh.System)
result = append(result, systemBytes...)
return result
}
/*-------------------------------------
secs包
-------------------------------------*/
type SecsPacket struct {
Header *SecsHeader
Data []byte
}
func (sp SecsPacket) String() string {
return fmt.Sprintf("%+v", sp.Header)
}
// Encode SecsPacket的编码函数
func (sp *SecsPacket) Encode() []byte {
var result []byte
//header 编码后的数据
headerData := sp.Header.Encode()
//length 消息的长度=headerData + data bytes
// 这里需要注意, 需要在最后面 加上两位 check sum(2字节), 但是这2字节不算在 length 中
length := byte(len(headerData) + len(sp.Data))
//check sum
checkSum := CalCheckSum(append(headerData, sp.Data...))
// block 组成格式:
// length + N-data bytes + check sum(2 bytes)
// N-data bytes= 10 header + message data
result = append(result, length)
result = append(result, headerData...)
result = append(result, sp.Data...)
result = append(result, checkSum...)
return result
}
/*-------------------------------------
解析
header=10
注意
1. data是slice类型会被覆盖 ???
-------------------------------------*/
func Decode(data []byte) *SecsPacket {
var pkt SecsPacket
var header SecsHeader
header.CheckSumPassed = false // 语义更加清晰, 默认失败
pkt.Header = &header
if len(data) < 12 {
// 最少有 10个字节的 header + 2 字节的 check sum
return &pkt
}
//检查checksum
var checkSum uint16
for _, i := range data[:len(data)-2] {
checkSum += uint16(i)
}
header.CheckSum = binary.BigEndian.Uint16(data[len(data)-2:])
header.CheckSumPassed = header.CheckSum == checkSum // 检查校验是否正确
if !header.CheckSumPassed {
return &pkt
}
header.IsToHost = binary.BigEndian.Uint16(data[:2])>>15 == 1
// & 与运算, 1 & x 的结果都是 x
header.SessionID = binary.BigEndian.Uint16(data[:2]) & 0b0111111111111111
header.RequireResponse = ((data[2] & 0b10000000) >> 7) == 1
header.Stream = data[2] & 0b01111111
header.Function = data[3]
header.IsLastBlock = binary.BigEndian.Uint16(data[4:6])>>15 == 1
header.BlockNumber = binary.BigEndian.Uint16(data[4:6]) & 0b0111111111111111
header.System = binary.BigEndian.Uint32(data[6:10])
pkt.Header = &header
// 前 10 个字节是 header
pkt.Data = data[10 : len(data)-2]
return &pkt
}
// CalCheckSum 校验码 将 data 所有的数值求和
func CalCheckSum(data []byte) []byte {
var total uint16
total = 0
for _, d := range data {
total += uint16(d)
}
var result = make([]byte, 2)
binary.BigEndian.PutUint16(result, total)
return result
}
2、单元测试用例
package packets
import (
"fmt"
"testing"
)
func TestDecode(t *testing.T) {
// 定义一个测试用例类型
type test struct {
input []byte
want *SecsPacket
}
s1f1H := &SecsHeader{
IsToHost: false,
SessionID: 0,
RequireResponse: true,
Stream: 1,
Function: 1,
IsLastBlock: true,
// end-bit
// 0: 还有数据传输
// 1: 最后一个块
BlockNumber: 1,
System: 0x269fbf56,
}
w1 := &SecsPacket{
Header: s1f1H,
Data: nil,
}
s1f14H := &SecsHeader{
IsToHost: true,
SessionID: 0,
RequireResponse: false,
Stream: 1,
Function: 14,
IsLastBlock: true,
BlockNumber: 1,
System: 0x269fbf59,
}
w2 := &SecsPacket{
Header: s1f14H,
Data: []byte{1, 0, 1, 2, 65, 10, 115, 105, 109, 117, 108, 97, 116, 105, 111, 110, 65, 5, 50, 46, 48, 46, 56, 8, 225},
}
// 定义一个存储测试用例的切片
tests := []test{
// 此数据是 host -> eqp
// 发送方发送的数据, 解码时为什么不需要第一个字节?
// 因为根据协议, 第一个字节表示 block data 的长度, 那后面的才是 sces-i 协议数据
// 因为日志中是编码过后在打印的数据, 所以包含了 block length(即第一个字节)
// {input: []byte{10, 0, 0, 129, 1, 128, 1, 38, 159, 191, 86, 2, 221}, want: w1},
{input: []byte{0, 0, 129, 1, 128, 1, 38, 159, 191, 86, 2, 221}, want: w1},
// 31 128 0 1 14 128 1 38 159 191 89 1 2 33 1 0 1 2 65 10 115 105 109 117 108 97 116 105 111 110 65 5 50 46 48 46 56 8 225
{input: []byte{128, 0, 1, 14, 128, 1, 38, 159, 191, 89, 1, 2, 33, 1, 0, 1, 2, 65, 10, 115, 105, 109, 117, 108, 97, 116, 105, 111, 110, 65, 5, 50, 46, 48, 46, 56, 8, 225}, want: w2},
}
for _, v := range tests {
get := Decode(v.input)
//fmt.Println(fmt.Sprintf("%v", get))
//fmt.Println(fmt.Sprintf("%v", v.want))
if fmt.Sprintf("%v", get) != fmt.Sprintf("%v", v.want) {
t.Fatalf("secs-i packets decode failed, get: %+v, want: %+v", get, v.want)
}
}
}
标签:协议,12,fmt,header,SECS,want,byte,data
From: https://www.cnblogs.com/huageyiyangdewo/p/17660255.html