Skip to content

Commit

Permalink
feat: 增加通用应答等指令解析
Browse files Browse the repository at this point in the history
  • Loading branch information
cuteLittleDevil committed Oct 7, 2024
1 parent 06032be commit 25845fc
Show file tree
Hide file tree
Showing 11 changed files with 573 additions and 32 deletions.
75 changes: 43 additions & 32 deletions protocol/jt808/jt808.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,64 @@ import (

type (
Header struct {
ID uint16 `json:"ID,omitempty"` // 消息ID
Property *BodyProperty `json:"property"` // 消息属性
// 协议版本 1-2011 2-2013 3-2019
// ID 消息ID
ID uint16 `json:"ID,omitempty"`
// Property 消息属性
Property *BodyProperty `json:"property"`
// ProtocolVersion 协议版本 1-2011 2-2013 3-2019
ProtocolVersion consts.ProtocolVersionType `json:"protocolVersion,omitempty"`
// 根据安装后终端自身的手机号转换。手机号不足 12 位,则在前补充数字
// TerminalPhoneNo 根据安装后终端自身的手机号转换。手机号不足 12 位,则在前补充数字
// 大陆手机号补充数字 0,港澳台则根据其区号进行位数补充
TerminalPhoneNo string `json:"terminalPhoneNo,omitempty"`
// 占用两个字节,为发送信息的序列号,用于接收方检测是否有信息的丢失,
// SerialNumber 占用两个字节,为发送信息的序列号,用于接收方检测是否有信息的丢失,
// 上级平台和下级平台接自己发送数据包的个数计数,互不影响。
// 程序开始运行时等于零,发送第一帧数据时开始计数,到最大数后自动归零
SerialNumber uint16 `json:"serialNumber,omitempty"`
SubPackageSum uint16 `json:"subPackageSum,omitempty"` // 消息总包数 不分包的时候为0
SubPackageNo uint16 `json:"subPackageNo,omitempty"` // 消息包序号
SerialNumber uint16 `json:"serialNumber,omitempty"`
// SubPackageSum 消息总包数 不分包的时候为0
SubPackageSum uint16 `json:"subPackageSum,omitempty"`
// SubPackageNo 消息包序号
SubPackageNo uint16 `json:"subPackageNo,omitempty"`

PlatformSerialNumber uint16 `json:"platformSerialNumber,omitempty"` // 平台的流水号
ReplyID uint16 `json:"replyID,omitempty"` // 平台回复的消息ID
// PlatformSerialNumber 平台的流水号
PlatformSerialNumber uint16 `json:"platformSerialNumber,omitempty"`
// ReplyID 平台回复的消息ID
ReplyID uint16 `json:"replyID,omitempty"`

headEnd int // 请求头结束位置
bcdTerminalPhoneNo []byte // 设备上传的bcd编码的手机号
// headEnd 请求头结束位置
headEnd int
// bcdTerminalPhoneNo 设备上传的bcd编码的手机号
bcdTerminalPhoneNo []byte
}

BodyProperty struct {
Version uint8 `json:"version,omitempty"` // 协议版本
// 分包标识,1:长消息,有分包;2:无分包
// Version 协议版本 jt808协议的
Version uint8 `json:"version,omitempty"`
// PacketFragmented 分包标识,1:长消息,有分包;2:无分包
PacketFragmented uint8 `json:"packetFragmented,omitempty"`
// 加密标识,0为不加密
// EncryptMethod 加密标识,0为不加密
// 当此三位都为 0,表示消息体不加密;
// 当第 10 位为 1,表示消息体经过 RSA 算法加密;
EncryptMethod uint8 `json:"encryptMethod,omitempty"`
BodyDayaLen uint16 `json:"bodyDayaLen,omitempty"` // 消息体长度
EncryptMethod uint8 `json:"encryptMethod,omitempty"`
// BodyDayaLen 消息体长度
BodyDayaLen uint16 `json:"bodyDayaLen,omitempty"`

attribute uint16 // 消息属性的原始数据
bit15 byte // 保留位
bit14 byte // 2013版本的保留 2019版本为1
isSubPackage bool // 是否分包
// attribute 消息属性的原始数据
attribute uint16
// bit15 保留位
bit15 byte
// bit14 2013版本的保留 2019版本为1
bit14 byte
// 是否分包
isSubPackage bool
}

JTMessage struct {
Header *Header `json:"header"`
VerifyCode byte `json:"-"` // 校验码
Body []byte `json:"body"`
// Header 请求头 固定格式的
Header *Header `json:"header"`
// VerifyCode 校验码
VerifyCode byte `json:"-"`
// Body 有效数据
Body []byte `json:"body"`
}
)

Expand Down Expand Up @@ -150,14 +167,8 @@ func (p *BodyProperty) decode(data []byte) {
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
p.EncryptMethod = uint8((attribute & 0x400) >> 10) // 第10-12位 加密方式 0-不加密 1-RSA
p.BodyDayaLen = attribute & 0x3FF // 最低10位 消息体长度 3=011 F=1111
}

func (p *BodyProperty) encode() uint16 {
Expand Down
31 changes: 31 additions & 0 deletions protocol/model/base_handle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package model

import (
"github.com/cuteLittleDevil/go-jt808/protocol/jt808"
"github.com/cuteLittleDevil/go-jt808/shared/consts"
)

type BaseHandle struct{}

func (b *BaseHandle) Parse(_ *jt808.JTMessage) error {
return nil
}

func (b *BaseHandle) HasReply() bool {
return true
}

func (b *BaseHandle) ReplyBody(jtMsg *jt808.JTMessage) []byte {
head := jtMsg.Header
// 通用应答
p8001 := &P0x8001{
RespondSerialNumber: head.SerialNumber,
RespondID: head.ID,
Result: 0x00, // 0-成功 1-失败 2-消息有误 3-不支持 4-报警处理确认 默认成功
}
return p8001.Encode()
}

func (b *BaseHandle) ReplyProtocol() uint16 {
return uint16(consts.P8001GeneralRespond)
}
61 changes: 61 additions & 0 deletions protocol/model/p_0x8001.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package model

import (
"encoding/binary"
"fmt"
"github.com/cuteLittleDevil/go-jt808/protocol"
"github.com/cuteLittleDevil/go-jt808/protocol/jt808"
"github.com/cuteLittleDevil/go-jt808/shared/consts"
"strings"
)

type P0x8001 struct {
// RespondSerialNumber 应答消息流水号 = 这个消息发送时候的流水号
RespondSerialNumber uint16
// RespondID 应答消息ID = 这个消息发送时候的ID
RespondID uint16
// Result 结果 // 0-成功 1-失败 2-消息有误 3-不支持 4-报警处理确认
Result byte
}

func (p *P0x8001) Protocol() uint16 {
return uint16(consts.P8001GeneralRespond)
}

func (p *P0x8001) ReplyProtocol() uint16 {
return 0
}

func (p *P0x8001) Parse(jtMsg *jt808.JTMessage) error {
body := jtMsg.Body
if len(body) != 5 {
return protocol.ErrBodyLengthInconsistency
}
p.RespondSerialNumber = binary.BigEndian.Uint16(body[:2])
p.RespondID = binary.BigEndian.Uint16(body[2:4])
p.Result = body[4]
return nil
}

func (p *P0x8001) Encode() []byte {
return []byte{
byte(p.RespondSerialNumber >> 8),
byte(p.RespondSerialNumber & 0xFF),
byte(p.RespondID >> 8),
byte(p.RespondID & 0xFF),
p.Result,
}
}

func (p *P0x8001) String() string {
str := "数据体对象:{\n"
str += fmt.Sprintf("\t%s:[%10x]", consts.P8001GeneralRespond, p.Encode())
return strings.Join([]string{
str,
fmt.Sprintf("\t[%04x] 应答消息流水号:[%d]", p.RespondSerialNumber, p.RespondSerialNumber),
fmt.Sprintf("\t[%04x] 应答消息ID:[%d]", p.RespondID, p.RespondID),
fmt.Sprintf("\t[%02x] 结果:[%d] 0-成功 1-失败 "+
"2-消息有误 3-不支持 4-报警处理确认", p.Result, p.Result),
"}",
}, "\n")
}
64 changes: 64 additions & 0 deletions protocol/model/p_0x8100.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package model

import (
"encoding/binary"
"fmt"
"github.com/cuteLittleDevil/go-jt808/protocol"
"github.com/cuteLittleDevil/go-jt808/protocol/jt808"
"github.com/cuteLittleDevil/go-jt808/protocol/utils"
"github.com/cuteLittleDevil/go-jt808/shared/consts"
"strings"
)

type P0x8100 struct {
// RespondSerialNumber 应答消息流水号 = 这个消息发送时候的流水号
RespondSerialNumber uint16
// Result 结果 0-成功 1-车辆已被注册 2-数据库中无该车辆 3-终端已被注册 4-数据库中无该终端
Result byte
// AuthCode 鉴权码
AuthCode string
}

func (p *P0x8100) Protocol() uint16 {
return uint16(consts.P8100RegisterRespond)
}

func (p *P0x8100) ReplyProtocol() uint16 {
return 0
}

func (p *P0x8100) Encode() []byte {
code := utils.String2FillingBytes(p.AuthCode, len(p.AuthCode))
tmp := []byte{
byte(p.RespondSerialNumber >> 8),
byte(p.RespondSerialNumber & 0xFF),
p.Result,
}
tmp = append(tmp, code...)
return tmp
}

func (p *P0x8100) Parse(jtMsg *jt808.JTMessage) error {
body := jtMsg.Body
if len(body) < 3 {
return protocol.ErrBodyLengthInconsistency
}
p.RespondSerialNumber = binary.BigEndian.Uint16(body[:2])
p.Result = body[2]
p.AuthCode = string(body[3:])
return nil
}

func (p *P0x8100) String() string {
str := "数据体对象:{\n"
body := p.Encode()
str += fmt.Sprintf("\t注册消息应答:[%x]", body)
return strings.Join([]string{
str,
fmt.Sprintf("\t[%04x] 应答流水号:[%d]", p.RespondSerialNumber, p.RespondSerialNumber),
fmt.Sprintf("\t[%02x] 结果:[%d] 0-成功 1-失败 "+
"2-消息有误 3-不支持 4-报警处理确认", p.Result, p.Result),
fmt.Sprintf("\t[%x] 鉴权码:[%s]", body[3:], p.AuthCode),
"}",
}, "\n")
}
104 changes: 104 additions & 0 deletions protocol/model/parse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package model

import (
"encoding/hex"
"errors"
"github.com/cuteLittleDevil/go-jt808/protocol"
"github.com/cuteLittleDevil/go-jt808/protocol/jt808"
"testing"
)

func TestParse(t *testing.T) {
type Handler interface {
Parse(*jt808.JTMessage) error
String() string
}
type args struct {
msg string
Handler
body []byte // 用于覆盖率100测试 强制替换了解析正确的body
}
tests := []struct {
name string
fields Handler
args args
}{
{
name: "T0X0001 终端-通用应答",
args: args{
msg: "7e000100050123456789017fff007b01c803bd7e",
Handler: &T0x0001{},
body: []byte{0, 123, 1, 200},
},
fields: &T0x0001{
SerialNumber: 123,
ID: 456,
Result: 3,
},
},
{
name: "P0X8001 平台-通用应答",
args: args{
msg: "7e8001000501234567890100007fff0002008e7e",
Handler: &P0x8001{},
body: []byte{0, 0, 0, 0},
},
fields: &P0x8001{
RespondSerialNumber: 32767,
RespondID: 2,
Result: 0,
},
},
{
name: "P0X8100 终端-注册消息应答",
args: args{
msg: "7e8100000e01234567890100000000003132333435363738393031377e",
Handler: &P0x8100{},
body: []byte{0, 0},
},
fields: &P0x8100{
RespondSerialNumber: 0,
Result: 0,
AuthCode: "12345678901",
},
},
{
name: "T0X0002 终端-心跳",
args: args{
msg: "7e0002000001234567890100008a7e",
Handler: &T0x0002{},
},
fields: &T0x0002{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, _ := hex.DecodeString(tt.args.msg)
jtMsg := jt808.NewJTMessage()
if err := jtMsg.Decode(data); err != nil {
t.Errorf("Decode() error = %v", err)
return
}
if err := tt.args.Parse(jtMsg); err != nil {
t.Errorf("Parse() error = %v", err)
return
}

//fmt.Println(tt.args.Handler.String())
if tt.args.Handler.String() != tt.fields.String() {
t.Errorf("Parse() want: \n%v\nactual:\n%v", tt.args, tt.fields)
return
}
if tt.args.body != nil {
jtMsg.Body = tt.args.body
if err := tt.args.Parse(jtMsg); err != nil {
if errors.Is(err, protocol.ErrBodyLengthInconsistency) {
return
}
t.Errorf("Parse() error = %v", err)
return
}
}
})
}
}
Loading

0 comments on commit 25845fc

Please sign in to comment.