diff --git a/protocol/error.go b/protocol/error.go new file mode 100644 index 0000000..850ab80 --- /dev/null +++ b/protocol/error.go @@ -0,0 +1,10 @@ +package protocol + +import "errors" + +var ( + ErrUnqualifiedData = errors.New("unqualified data") + ErrHeaderLength2Short = errors.New("header length too short") + ErrBodyLengthInconsistency = errors.New("body length inconsistency") + ErrCheckCode = errors.New("check code fail") +) diff --git a/protocol/go.mod b/protocol/go.mod new file mode 100644 index 0000000..d554adf --- /dev/null +++ b/protocol/go.mod @@ -0,0 +1,5 @@ +module github.com/cuteLittleDevil/go-jt808/protocol + +go 1.23.2 + +require github.com/cuteLittleDevil/go-jt808/shared v0.1.0 diff --git a/protocol/go.sum b/protocol/go.sum new file mode 100644 index 0000000..6bf0305 --- /dev/null +++ b/protocol/go.sum @@ -0,0 +1,2 @@ +github.com/cuteLittleDevil/go-jt808/shared v0.1.0 h1:Y6uDHbSFqa/VsHcGa713vrB0qz70xidXqDwEkFNe2ro= +github.com/cuteLittleDevil/go-jt808/shared v0.1.0/go.mod h1:BMWFmkDRLNjcXcuiPm/yphfWfZ6xNuTAJDkDDNhysOM= diff --git a/protocol/jt808/jt808.go b/protocol/jt808/jt808.go new file mode 100644 index 0000000..3f4763a --- /dev/null +++ b/protocol/jt808/jt808.go @@ -0,0 +1,204 @@ +package jt808 + +import ( + "bytes" + "encoding/binary" + "fmt" + "github.com/cuteLittleDevil/go-jt808/protocol" + "github.com/cuteLittleDevil/go-jt808/protocol/utils" + "github.com/cuteLittleDevil/go-jt808/shared/consts" + "strings" +) + +type ( + Header struct { + ID uint16 `json:"ID,omitempty"` // 消息ID + Property *BodyProperty `json:"property"` // 消息属性 + // 协议版本 1-2011 2-2013 3-2019 + ProtocolVersion consts.ProtocolVersionType `json:"protocolVersion,omitempty"` + // 根据安装后终端自身的手机号转换。手机号不足 12 位,则在前补充数字 + // 大陆手机号补充数字 0,港澳台则根据其区号进行位数补充 + TerminalPhoneNo string `json:"terminalPhoneNo,omitempty"` + // 占用两个字节,为发送信息的序列号,用于接收方检测是否有信息的丢失, + // 上级平台和下级平台接自己发送数据包的个数计数,互不影响。 + // 程序开始运行时等于零,发送第一帧数据时开始计数,到最大数后自动归零 + SerialNumber uint16 `json:"serialNumber,omitempty"` + SubPackageSum uint16 `json:"subPackageSum,omitempty"` // 消息总包数 不分包的时候为0 + SubPackageNo uint16 `json:"subPackageNo,omitempty"` // 消息包序号 + + PlatformSerialNumber uint16 `json:"platformSerialNumber,omitempty"` // 平台的流水号 + ReplyID uint16 `json:"replyID,omitempty"` // 平台回复的消息ID + + headEnd int // 请求头结束位置 + bcdTerminalPhoneNo []byte // 设备上传的bcd编码的手机号 + } + + BodyProperty struct { + Version uint8 `json:"version,omitempty"` // 协议版本 + // 分包标识,1:长消息,有分包;2:无分包 + PacketFragmented uint8 `json:"packetFragmented,omitempty"` + // 加密标识,0为不加密 + // 当此三位都为 0,表示消息体不加密; + // 当第 10 位为 1,表示消息体经过 RSA 算法加密; + EncryptMethod uint8 `json:"encryptMethod,omitempty"` + BodyDayaLen uint16 `json:"bodyDayaLen,omitempty"` // 消息体长度 + + attribute uint16 // 消息属性的原始数据 + bit15 byte // 保留位 + bit14 byte // 2013版本的保留 2019版本为1 + isSubPackage bool // 是否分包 + } + + JTMessage struct { + Header *Header `json:"header"` + VerifyCode byte `json:"-"` // 校验码 + Body []byte `json:"body"` + } +) + +func NewJTMessage() *JTMessage { + return &JTMessage{ + Header: &Header{ + Property: &BodyProperty{}, + }, + VerifyCode: 0, + Body: nil, + } +} + +func (j *JTMessage) Decode(data []byte) error { + escapeData, err := unescape(data) + if err != nil { + return err + } + if code := utils.CreateVerifyCode(escapeData); code != 0 { + return protocol.ErrCheckCode + } + if err := j.Header.decode(escapeData); err != nil { + return err + } + start := j.Header.headEnd + end := start + int(j.Header.Property.BodyDayaLen) + if end+1 != len(escapeData) { + return protocol.ErrBodyLengthInconsistency + } + j.Body = escapeData[start:end] + j.VerifyCode = escapeData[end] + return nil +} + +func (h *Header) decode(data []byte) error { + if len(data) < 4 { + return protocol.ErrHeaderLength2Short + } + h.ID = binary.BigEndian.Uint16(data[0:2]) + h.Property.decode(data[2:4]) + var ( + start = 4 + phoneLen = 6 + version = consts.JT808Protocol2013 // 默认2013版本 + ) + if h.Property.Version == 1 { + start = 5 + phoneLen = 10 + version = consts.JT808Protocol2019 + } + if len(data) < start+phoneLen+2 { + return protocol.ErrHeaderLength2Short + } + h.ProtocolVersion = version + h.bcdTerminalPhoneNo = data[start : start+phoneLen] + h.TerminalPhoneNo = utils.Bcd2Dec(h.bcdTerminalPhoneNo) + h.SerialNumber = binary.BigEndian.Uint16(data[start+phoneLen : start+phoneLen+2]) + end := start + phoneLen + 2 + if h.Property.isSubPackage { + if len(data) < start+phoneLen+6 { + return protocol.ErrHeaderLength2Short + } + h.SubPackageSum = binary.BigEndian.Uint16(data[start+phoneLen+2 : start+phoneLen+4]) + h.SubPackageNo = binary.BigEndian.Uint16(data[start+phoneLen+4 : start+phoneLen+6]) + end += 4 + } + h.headEnd = end + return nil +} + +func (h *Header) Encode(body []byte) []byte { + buf := new(bytes.Buffer) + _ = binary.Write(buf, binary.BigEndian, h.ReplyID) // 写ID 回复消息的ID + h.Property.BodyDayaLen = uint16(len(body)) // 消息的长度改为回复的body长度 + _ = binary.Write(buf, binary.BigEndian, h.Property.encode()) // 写消息属性 + if h.ProtocolVersion == consts.JT808Protocol2019 { + // 2019版本的标识 + buf.WriteByte(0x01) + } + buf.Write(h.bcdTerminalPhoneNo) // 写终端手机号 + _ = binary.Write(buf, binary.BigEndian, h.PlatformSerialNumber) // 写流水号 平台回复的流水号 + buf.Write(body) // 写消息体 + code := utils.CreateVerifyCode(buf.Bytes()) // 校验码 + buf.WriteByte(code) + return escape(buf.Bytes()) // 转义 +} + +func (p *BodyProperty) decode(data []byte) { + attribute := binary.BigEndian.Uint16(data) + p.attribute = attribute + p.bit15 = byte(attribute & 0x8000) // 第15位 保留 + p.bit14 = byte((attribute >> 14) & 0b1) // 第14位 协议版本 0-2013 1-2019 + p.Version = p.bit14 + p.PacketFragmented = byte((attribute >> 13) & 0b1) // 第13位 分包 + if p.PacketFragmented == 1 { + p.isSubPackage = true + } + switch (attribute & 0x400) >> 10 { // 第10-12位 加密方式 + case 0: + p.EncryptMethod = 0 // 不加密 + case 1: + p.EncryptMethod = 1 // RSA算法 + default: + } + p.BodyDayaLen = attribute & 0x3FF // 最低10位 消息体长度 3=011 F=1111 +} + +func (p *BodyProperty) encode() uint16 { + return (uint16(p.bit15) << 15) | // 第15位 保留 + (uint16(p.Version) << 14) | // 第14位 协议版本 0-2013 1-2019 + (uint16(p.PacketFragmented) << 13) | // 第13位 分包 + (uint16(p.EncryptMethod) << 10) | // 第10位 加密方式 + p.BodyDayaLen // 最低10位 消息体长度 +} + +func (h *Header) String() string { + str := "" + switch h.ProtocolVersion { + case consts.JT808Protocol2019: + str += "[01] 协议版本号(2019):[1]\n" + str += fmt.Sprintf("[%20x] 终端手机号:[%s]", h.bcdTerminalPhoneNo, h.TerminalPhoneNo) + default: + str += fmt.Sprintf("[%12x] 终端手机号:[%s]", h.bcdTerminalPhoneNo, h.TerminalPhoneNo) + } + return strings.Join([]string{ + fmt.Sprintf("[%04x] 消息ID:[%d] [%s]", h.ID, h.ID, consts.TerminalRequestType(h.ID)), + h.Property.String(), + str, + fmt.Sprintf("[%04x] 消息流水号:[%d]", h.SerialNumber, h.SerialNumber), + }, "\n") +} + +func (p *BodyProperty) String() string { + version := consts.JT808Protocol2013 + if p.Version == 1 { + version = consts.JT808Protocol2019 + } + return strings.Join([]string{ + "消息体属性对象: {", + fmt.Sprintf("\t[%016b] 消息体属性对象:[%d]", p.attribute, p.attribute), + fmt.Sprintf("\t版本号:[%s]", version.String()), + fmt.Sprintf("\t[bit15] [%d]", p.bit15), + fmt.Sprintf("\t[bit14] 协议版本标识:[%d]", p.bit14), + fmt.Sprintf("\t[bit13] 是否分包:[%t]", p.isSubPackage), + fmt.Sprintf("\t[bit10-12] 加密标识:[%d] 0-不加密 1-RSA", p.EncryptMethod), + fmt.Sprintf("\t[bit0-bit9] 消息体长度:[%d]", p.BodyDayaLen), + "}", + }, "\n") +} diff --git a/protocol/jt808/jt808_test.go b/protocol/jt808/jt808_test.go new file mode 100644 index 0000000..e3028de --- /dev/null +++ b/protocol/jt808/jt808_test.go @@ -0,0 +1,148 @@ +package jt808 + +import ( + "encoding/hex" + "errors" + "fmt" + "github.com/cuteLittleDevil/go-jt808/protocol" + "os" + "reflect" + "testing" +) + +func TestJTMessage_Decode(t *testing.T) { + tests := []struct { + name string + args string + wantErr error + }{ + { + name: "2013版本", + args: "7e0100002c0123456789010000001f0073797a6800007777772e6a74743830382e636f6d0000000000003736353433323101b2e24131323334ca7e", + }, + { + name: "2019版本", + args: "7e0100405301000000000172998417380000001f0073797a6800000000000000007777772e6a74743830382e636f6d0000000000000000000000000000000037363534333231000000000000000000000000000000000000000000000001b2e241313233343d7e", + }, + { + name: "正确的分包数据", + args: "7E0801200500123456789002DE001A00022808000102537E", + }, + { + name: "RSA加密数据", + args: "7E0801040500123456789002DE001A000221757E", // 模拟生成的 仅标志位=1为RSA + }, + { + name: "不完整的数据", + args: "7e010040530100", + wantErr: protocol.ErrUnqualifiedData, + }, + { + name: "错误的数据", + args: "7e01017e", + wantErr: protocol.ErrHeaderLength2Short, + }, + { + name: "RSA加密数据", + args: "7E0801040500123456789002DE001A000221757E", // 模拟生成的 仅标志位=1为RSA + }, + { + name: "校验码错误", + args: "7E0801200500123456789002DE001A00022808000102547E", + wantErr: protocol.ErrCheckCode, + }, + { + name: "body数据和解析头不符合", + args: "7E0801200500123456789002DE001A000228080001517E", + wantErr: protocol.ErrBodyLengthInconsistency, + }, + { + name: "头部情况不足", + args: "7e0100002c0123454a7e", + wantErr: protocol.ErrHeaderLength2Short, + }, + { + name: "头部情况不足 分包情况", + args: "7E0801200500123456789002b67E", + wantErr: protocol.ErrHeaderLength2Short, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + jtMsg := NewJTMessage() + arg, _ := hex.DecodeString(tt.args) + if err := jtMsg.Decode(arg); err != nil { + if !errors.Is(err, tt.wantErr) { + t.Errorf("Decode() error = %v, wantErr %v", err, tt.wantErr) + } + return + } + }) + } +} + +func TestEncode(t *testing.T) { + tests := []struct { + name string + args string + want string + }{ + { + name: "2013版本", + args: "7e0002000001234567890100008a7e", + want: "7e000000000123456789010000887e", + }, + { + name: "2019版本", + args: "7e0002400001000000000172998417380000027e", + want: "7e0000400001000000000172998417380000007e", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + jtMsg := NewJTMessage() + head, _ := hex.DecodeString(tt.args) + _ = jtMsg.Decode(head) + data := jtMsg.Header.Encode(nil) + got := fmt.Sprintf("%x", data) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Encode() = %s\n want %s", got, tt.want) + } + }) + } +} + +func TestString(t *testing.T) { + tests := []struct { + name string + args string + want string + }{ + { + name: "2013版本", + args: "7e0002000001234567890100008a7e", + want: "./txt/head_2013.txt", + }, + { + name: "2019版本", + args: "7e0002400001000000000172998417380000027e", + want: "./txt/head_2019.txt", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + jtMsg := NewJTMessage() + head, _ := hex.DecodeString(tt.args) + _ = jtMsg.Decode(head) + got := jtMsg.Header.String() + txt, err := os.ReadFile(tt.want) + if err != nil { + t.Errorf("open file [%s] [%v]", tt.want, err) + return + } + if !reflect.DeepEqual(got, string(txt)) { + t.Errorf("Encode() = %s\n want %s", got, string(txt)) + } + }) + } +} diff --git a/protocol/jt808/packet_codec.go b/protocol/jt808/packet_codec.go new file mode 100644 index 0000000..cc99088 --- /dev/null +++ b/protocol/jt808/packet_codec.go @@ -0,0 +1,69 @@ +package jt808 + +import ( + "bytes" + "github.com/cuteLittleDevil/go-jt808/protocol" +) + +func unescape(data []byte) ([]byte, error) { + const ( + beforeEscape = 0x7d + afterRecover = 0x7e + ) + if !(len(data) > 2 && data[0] == afterRecover && data[len(data)-1] == afterRecover) { + return nil, protocol.ErrUnqualifiedData + } + // 快速路径 没有需要转义的 + if !bytes.ContainsRune(data, beforeEscape) { + return data[1 : len(data)-1], nil + } + + buf := new(bytes.Buffer) + index := 1 + for i := 1; i < len(data)-1; i++ { + if v := data[i]; v == beforeEscape { + i++ + switch data[i] { + case 0x01: + buf.Write(data[index : i-1]) + buf.WriteByte(beforeEscape) + case 0x02: + buf.Write(data[index : i-1]) + buf.WriteByte(afterRecover) + default: + return nil, protocol.ErrUnqualifiedData + } + index = i + 1 + } + } + if index != len(data)-1 { + buf.Write(data[index : len(data)-1]) + } + return buf.Bytes(), nil +} + +func escape(data []byte) []byte { + const ( + flag0x7d = 0x7d // 转义标志 0x7d -> 0x7d 0x01 + flag0x7e = 0x7e // 转义标志 0x7e -> 0x7d 0x02 + ) + buf := new(bytes.Buffer) + buf.WriteByte(flag0x7e) + index := 0 + for i := 0; i < len(data); i++ { + switch data[i] { + case flag0x7e: + buf.Write(data[index:i]) + buf.Write([]byte{0x7d, 0x02}) + index = i + 1 + case flag0x7d: + buf.Write(data[index:i]) + buf.Write([]byte{0x7d, 0x01}) + index = i + 1 + default: + } + } + buf.Write(data[index:]) + buf.WriteByte(flag0x7e) + return buf.Bytes() +} diff --git a/protocol/jt808/packet_codec_test.go b/protocol/jt808/packet_codec_test.go new file mode 100644 index 0000000..7815139 --- /dev/null +++ b/protocol/jt808/packet_codec_test.go @@ -0,0 +1,138 @@ +package jt808 + +import ( + "encoding/hex" + "errors" + "github.com/cuteLittleDevil/go-jt808/protocol" + "reflect" + "testing" +) + +func Test_unescape(t *testing.T) { + tests := []struct { + name string + args string + want string + wantErr error + }{ + { + name: "不需要反转义的", + args: "7e000140050100000000017299841738ffff007b01c803b57e", + want: "000140050100000000017299841738ffff007b01c803b5", + }, + { + name: "需要反转义的", + args: "7e0200401c01000000000172998417380000000004000000080007203b7d0202633df7013800030063200707192359c17e", + want: "0200401c01000000000172998417380000000004000000080007203b7e02633df7013800030063200707192359c1", + }, + { + name: "错误的数据 内容带7e并且后面不是01或者02", + args: "7e02007d037e", + wantErr: protocol.ErrUnqualifiedData, + }, + { + name: "错误的数据", + args: "7e02", + wantErr: protocol.ErrUnqualifiedData, + }, + { + name: "全部需要转义的", + args: "7e02007d0100107d027e", + want: "02007d00107e", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + arg, _ := hex.DecodeString(tt.args) + got, err := unescape(arg) + if err != nil { + if !errors.Is(err, tt.wantErr) { + t.Errorf("unescape() error = %v, wantErr %v", err, tt.wantErr) + } + return + } + want, _ := hex.DecodeString(tt.want) + if !reflect.DeepEqual(got, want) { + t.Errorf("unescape2() = %x\n want %x", got, want) + } + }) + } +} + +func Benchmark_unescape(b *testing.B) { + // 假设10个数据里面 7个不需要转义 2个转义1次 1个转义2次 + escapeZero := "7e000140050100000000017299841738ffff007b01c803b57e" + escapeOne := "7e080040080100000000017299841738ffff0000007b000007017d017e" + escapeTwo := "7e08007d0240080100000000017299841738ffff0000007b000007017d017e" + datas := make([][]byte, 0, 10) + for i := 0; i < 7; i++ { + arg, _ := hex.DecodeString(escapeZero) + datas = append(datas, arg) + } + for i := 0; i < 2; i++ { + arg, _ := hex.DecodeString(escapeOne) + datas = append(datas, arg) + } + for i := 0; i < 1; i++ { + arg, _ := hex.DecodeString(escapeTwo) + datas = append(datas, arg) + } + for i := 0; i < b.N; i++ { + for _, data := range datas { + _, _ = unescape(data) + } + } +} + +func Test_escape(t *testing.T) { + tests := []struct { + name string + args string + want string + }{ + { + name: "不需要转义的", + args: "000140050100000000017299841738ffff007b01c803b5", + want: "7e000140050100000000017299841738ffff007b01c803b57e", + }, + { + name: "需要转义的", + args: "0800407e08010000000001727d99841738ffff0000007b000007017d", + want: "7e0800407d0208010000000001727d0199841738ffff0000007b000007017d017e", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + arg, _ := hex.DecodeString(tt.args) + want, _ := hex.DecodeString(tt.want) + if got := escape(arg); !reflect.DeepEqual(got, want) { + t.Errorf("unescape2() = %x\n want %x", got, want) + } + }) + } +} + +func Benchmark_escape(b *testing.B) { + // 假设10个数据里面 7个不需要转义 2个转义1次 1个转义2次 + escapeZero := "000140050100000000017299841738ffff007b01c803b5" + escapeOne := "080040080100000000017299841738ffff0000007b000007017d01" + escapeTwo := "08007d0240080100000000017299841738ffff0000007b000007017d01" + datas := make([][]byte, 0, 10) + for i := 0; i < 7; i++ { + arg, _ := hex.DecodeString(escapeZero) + datas = append(datas, arg) + } + for i := 0; i < 2; i++ { + arg, _ := hex.DecodeString(escapeOne) + datas = append(datas, arg) + } + for i := 0; i < 1; i++ { + arg, _ := hex.DecodeString(escapeTwo) + datas = append(datas, arg) + } + for i := 0; i < b.N; i++ { + for _, data := range datas { + _ = escape(data) + } + } +} diff --git a/protocol/jt808/txt/head_2013.txt b/protocol/jt808/txt/head_2013.txt new file mode 100644 index 0000000..126218a --- /dev/null +++ b/protocol/jt808/txt/head_2013.txt @@ -0,0 +1,12 @@ +[0002] 消息ID:[2] [终端-心跳] +消息体属性对象: { + [0000000000000000] 消息体属性对象:[0] + 版本号:[JT2013] + [bit15] [0] + [bit14] 协议版本标识:[0] + [bit13] 是否分包:[false] + [bit10-12] 加密标识:[0] 0-不加密 1-RSA + [bit0-bit9] 消息体长度:[0] +} +[012345678901] 终端手机号:[12345678901] +[0000] 消息流水号:[0] \ No newline at end of file diff --git a/protocol/jt808/txt/head_2019.txt b/protocol/jt808/txt/head_2019.txt new file mode 100644 index 0000000..8b1dd6b --- /dev/null +++ b/protocol/jt808/txt/head_2019.txt @@ -0,0 +1,13 @@ +[0002] 消息ID:[2] [终端-心跳] +消息体属性对象: { + [0100000000000000] 消息体属性对象:[16384] + 版本号:[JT2019] + [bit15] [0] + [bit14] 协议版本标识:[1] + [bit13] 是否分包:[false] + [bit10-12] 加密标识:[0] 0-不加密 1-RSA + [bit0-bit9] 消息体长度:[0] +} +[01] 协议版本号(2019):[1] +[00000000017299841738] 终端手机号:[17299841738] +[0000] 消息流水号:[0] \ No newline at end of file diff --git a/protocol/utils/utils.go b/protocol/utils/utils.go new file mode 100644 index 0000000..44a1a00 --- /dev/null +++ b/protocol/utils/utils.go @@ -0,0 +1,70 @@ +package utils + +import ( + "bytes" +) + +func Bcd2Dec(data []byte) string { + out := bcdConvert(data) + if noZero := bytes.IndexFunc(out, func(r rune) bool { + return r != '0' + }); noZero != -1 { + return string(out[noZero:]) + } + return string(out) +} + +func Time2BCD(time string) []byte { + //if len(time)%2 != 0 { + // time = "0" + time + //} + bcd := make([]byte, len(time)/2) + for i := 0; i < len(time); i += 2 { + // 高4位是第一个字符,低4位是第二个字符 + bcd[i/2] = ((time[i] - '0') << 4) | (time[i+1] - '0') + } + return bcd +} + +func bcdConvert(data []byte) []byte { + out := make([]byte, 2*len(data)) + index := 0 + for i := 0; i < len(data); i++ { + out[index] = nibbleToHexChar(data[i] >> 4) + index++ + + out[index] = nibbleToHexChar(data[i] & 0x0f) + index++ + } + return out +} + +func nibbleToHexChar(nibble byte) byte { + if nibble <= 9 { + return nibble + '0' + } + switch { + case nibble == 0x0a: + return 'a' + case nibble == 0x0b: + return 'b' + case nibble == 0x0c: + return 'c' + case nibble == 0x0d: + return 'd' + case nibble == 0x0e: + return 'e' + default: + } + // 0x0f bcd编码16进制 只有0-9和a-f + return 'f' + +} + +func CreateVerifyCode(data []byte) byte { + var code byte + for _, v := range data { + code ^= v + } + return code +} diff --git a/protocol/utils/utils_test.go b/protocol/utils/utils_test.go new file mode 100644 index 0000000..766a88a --- /dev/null +++ b/protocol/utils/utils_test.go @@ -0,0 +1,106 @@ +package utils + +import ( + "encoding/hex" + "reflect" + "testing" +) + +func TestBcd2Dec(t *testing.T) { + tests := []struct { + name string + args string + want string + }{ + { + name: "2013版本", + args: "012345678901", + want: "12345678901", + }, + { + name: "2019版本", + args: "00000000017299841738", + want: "17299841738", + }, + { + name: "不需要补0的", + args: "12345678", + want: "12345678", + }, + { + name: "奇数情况", + args: "123456789", + want: "12345678", + }, + { + name: "全是0", + args: "00000000", + want: "00000000", + }, + { + name: "字母和数字组合", + args: "abcdef1234567890ABCDEF", + want: "abcdef1234567890abcdef", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + arg, _ := hex.DecodeString(tt.args) + if got := Bcd2Dec(arg); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Bcd2Dec() got = %+v \n want %v", got, tt.want) + } + }) + } +} + +func BenchmarkBcd2Dec(b *testing.B) { + bcd2013, _ := hex.DecodeString("012345678901") + bcd2019, _ := hex.DecodeString("00000000017299841738") + + for i := 0; i < b.N; i++ { + Bcd2Dec(bcd2013) + Bcd2Dec(bcd2019) + } +} + +func TestCreateVerifyCode(t *testing.T) { + tests := []struct { + name string + args string + want byte + }{ + { + name: "2013版本", + args: "000100050123456789017fff007b01c803", + want: 0xbd, + }, + { + name: "2019版本", + args: "000140050100000000017299841738ffff007b01c803", + want: 0xb5, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + arg, _ := hex.DecodeString(tt.args) + if got := CreateVerifyCode(arg); !reflect.DeepEqual(got, tt.want) { + t.Errorf("unescape2() = %x\n want %x", got, tt.want) + } + }) + } +} + +func TestTime2BCD(t *testing.T) { + time := "200707192359" + bcd := Time2BCD(time) + want := []byte{32, 7, 7, 25, 35, 89} + if !reflect.DeepEqual(bcd, want) { + t.Errorf("Time2BCD() = %x\n want %x", bcd, want) + } +} + +func BenchmarkTime2BCD(b *testing.B) { + for i := 0; i < b.N; i++ { + Time2BCD("200707192359") + } +}