├── .gitignore ├── internal ├── const.go ├── bytes │ ├── checksum_test.go │ ├── checksum.go │ ├── escape_test.go │ ├── buffer_test.go │ ├── escape.go │ ├── buffer.go │ └── reader.go ├── generator │ ├── message.go │ ├── ast.go │ └── decl.go ├── tag │ └── tag.go ├── encode │ └── encode.go └── decode │ └── decode.go ├── message ├── 0002.go ├── 0003.go ├── 0004.go ├── mesg_body.go ├── 8100.go ├── 0001.go ├── 8001.go ├── 8003.go ├── 0005.go ├── 8004.go ├── 0801.go ├── 0102_13.go ├── partial.go ├── 0100_13.go ├── 0100_19.go ├── const.go ├── 0102_19_test.go ├── 0102_19.go ├── mesg_header_test.go ├── 0200.go ├── 0104_19.go ├── 0104_13.go ├── mesg_pack.gen.go ├── mesg_pack.go └── mesg_header.go ├── tools ├── tools.go └── generator │ └── decoder │ ├── gen_mesg_iface.go │ ├── main.go │ └── gen_newpkg_body_from_mesgid.go ├── error.go ├── encoder.go ├── go.mod ├── .github └── workflows │ └── go.yml ├── go.sum ├── README.md ├── decoder.go ├── encoder_test.go └── decoder_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | 4 | *.test 5 | *.sw[op] 6 | *.un~ 7 | *.iml 8 | -------------------------------------------------------------------------------- /internal/const.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | const IdentifyByte uint8 = 0x7e 4 | -------------------------------------------------------------------------------- /message/0002.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 终端心跳 4 | type Body0002 struct { 5 | } 6 | -------------------------------------------------------------------------------- /message/0003.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 终端注销 4 | type Body0003 struct { 5 | } 6 | -------------------------------------------------------------------------------- /message/0004.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 查询服务器时间 4 | type Body0004 struct { 5 | } 6 | -------------------------------------------------------------------------------- /message/mesg_body.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | type MesgBody interface { 4 | MesgId() uint16 5 | } 6 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | package tools 4 | 5 | import ( 6 | _ "github.com/dave/jennifer" 7 | ) 8 | -------------------------------------------------------------------------------- /message/8100.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 终端注册应答 4 | type Body8100 struct { 5 | SerialId uint16 6 | Result uint8 7 | AuthCode string 8 | } 9 | -------------------------------------------------------------------------------- /message/0001.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 终端通用应答 4 | type Body0001 struct { 5 | AckMesgId uint16 6 | AckSerialId uint16 7 | AckType uint8 8 | } 9 | -------------------------------------------------------------------------------- /message/8001.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 平台通用应答 4 | type Body8001 struct { 5 | AckSerialId uint16 6 | AckMesgId uint16 7 | AckType uint8 8 | } 9 | -------------------------------------------------------------------------------- /message/8003.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 终端注销 4 | type Body8003 struct { 5 | SerialId uint16 6 | TotalCount uint16 7 | RawMesgIds []byte `jt808:"-1,raw"` 8 | } 9 | -------------------------------------------------------------------------------- /message/0005.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 终端补传分包请求 4 | type Body0005 struct { 5 | SerialId uint16 6 | TotalCount uint16 7 | RawMesgIds []byte `jt808:"-1,raw"` 8 | } 9 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package jt808 2 | 3 | import "errors" 4 | 5 | var ErrNotPkgMesg = errors.New("not package message") 6 | var ErrIncompletedPkgMesg = errors.New("incompleted package message") 7 | -------------------------------------------------------------------------------- /encoder.go: -------------------------------------------------------------------------------- 1 | package jt808 2 | 3 | import ( 4 | "github.com/francistm/jt808-golang/message" 5 | ) 6 | 7 | func Marshal[T message.MesgBody](mesgPack *message.MessagePack[T]) ([]byte, error) { 8 | return mesgPack.MarshalBinary() 9 | } 10 | -------------------------------------------------------------------------------- /message/8004.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import "time" 4 | 5 | // 查询服务器时间应答 6 | type Body8004 struct { 7 | RawTime string `jt808:"6,bcd"` 8 | } 9 | 10 | func (b *Body8004) Time() time.Time { 11 | t, _ := time.ParseInLocation(timeLayoutBCD, b.RawTime, time.UTC) 12 | 13 | return t 14 | } 15 | -------------------------------------------------------------------------------- /message/0801.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 多媒体数据上传 4 | type Body0801 struct { 5 | MediaID uint32 6 | MediaType uint8 7 | MediaContentType uint8 8 | EventID uint8 9 | ChannelID uint8 10 | PackBody0200 Body0200Base 11 | MediaContent []byte `jt808:"-1,raw"` 12 | } 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/francistm/jt808-golang 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/dave/jennifer v1.7.0 7 | github.com/stretchr/testify v1.8.4 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /message/0102_13.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | type Body0102_13 struct { 4 | AuthCode string 5 | } 6 | 7 | func (body *Body0102_13) MarshalBinary() ([]byte, error) { 8 | return []byte(body.AuthCode), nil 9 | } 10 | 11 | func (body *Body0102_13) UnmarshalBinary(data []byte) error { 12 | body.AuthCode = string(data) 13 | 14 | return nil 15 | } 16 | -------------------------------------------------------------------------------- /internal/bytes/checksum_test.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_CalcChecksum(t *testing.T) { 10 | b := []byte{0x01, 0x02, 0x03} 11 | checksum, err := CalcChecksum(b) 12 | 13 | if assert.NoError(t, err) { 14 | assert.Equal(t, byte(0x00), checksum) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /message/partial.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | var ( 8 | timeLayoutBCD = "060102150405" 9 | timezoneCST = time.FixedZone("Asia/Shanghai", 8*3600) 10 | ) 11 | 12 | type PartialPackBody struct { 13 | RawBody []byte `jt808:"-1,raw"` 14 | } 15 | 16 | func (*PartialPackBody) MesgId() uint16 { 17 | return 0 18 | } 19 | -------------------------------------------------------------------------------- /message/0100_13.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 终端注册 4 | type Body0100_13 struct { 5 | Province uint16 6 | City uint16 7 | Manufacturer string `jt808:"5,raw"` 8 | DeviceModel string `jt808:"20,raw"` 9 | DeviceId string `jt808:"7,raw"` 10 | LicencePlateColor uint8 11 | LicencePlate string `jt808:"-1,gbk"` 12 | } 13 | -------------------------------------------------------------------------------- /message/0100_19.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // 终端注册 4 | type Body0100_19 struct { 5 | Province uint16 6 | City uint16 7 | Manufacturer string `jt808:"11,raw"` 8 | DeviceModel string `jt808:"30,raw"` 9 | DeviceId string `jt808:"30,raw"` 10 | LicencePlateColor uint8 11 | LicencePlate string `jt808:"-1,gbk"` 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: "UnitTest" 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: "1.20" 20 | 21 | - name: Test 22 | run: go test ./... 23 | -------------------------------------------------------------------------------- /tools/generator/decoder/gen_mesg_iface.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/dave/jennifer/jen" 5 | "github.com/francistm/jt808-golang/internal/generator" 6 | ) 7 | 8 | func genMesgBodyIface(f *File, mesgDecls []*generator.MesgDecl) { 9 | for _, mesgDecl := range mesgDecls { 10 | for _, version := range mesgDecl.Versions { 11 | if version == nil { 12 | continue 13 | } 14 | 15 | f.Func(). 16 | Params( 17 | Op("*").Id(version.StructName), 18 | ). 19 | Id("MesgId"). 20 | Params(). 21 | Uint16(). 22 | Block( 23 | Return(Lit(mesgDecl.MesgId)), 24 | ) 25 | 26 | f.Line() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/bytes/checksum.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | func CalcChecksum(buf []byte) (byte, error) { 10 | if len(buf) < 2 { 11 | return 0, errors.New("buf is less than 2 bytes") 12 | } 13 | 14 | reader := bytes.NewReader(buf) 15 | checksum, err := reader.ReadByte() 16 | 17 | if err != nil { 18 | return 0, err 19 | } 20 | 21 | for { 22 | var b byte 23 | var err error 24 | 25 | b, err = reader.ReadByte() 26 | 27 | if errors.Is(err, io.EOF) { 28 | break 29 | } 30 | 31 | if err != nil { 32 | return 0, err 33 | } 34 | 35 | checksum ^= b 36 | } 37 | 38 | return checksum, nil 39 | } 40 | -------------------------------------------------------------------------------- /internal/bytes/escape_test.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_Escape(t *testing.T) { 10 | unescaped := []byte{0x7d, 0x7e, 0x01, 0x02} 11 | escaped, err := Escape(unescaped) 12 | 13 | if assert.NoError(t, err) { 14 | assert.Equal(t, []byte{0x7d, 0x01, 0x7d, 0x02, 0x01, 0x02}, escaped) 15 | } 16 | } 17 | 18 | func Test_Unescape(t *testing.T) { 19 | escaped := []byte{0x7e, 0x7d, 0x02, 0x7d, 0x01, 0x03, 0x04, 0x7e} 20 | unescaped, err := Unescape(escaped) 21 | 22 | if assert.NoError(t, err) { 23 | assert.Equal(t, []byte{0x7e, 0x7e, 0x7d, 0x03, 0x04, 0x7e}, unescaped) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /message/const.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import "errors" 4 | 5 | const ( 6 | AckType_0001_Succeed byte = iota 7 | AckType_0001_Failed 8 | AckType_0001_Incorrect 9 | AckType_0001_Unsupport 10 | ) 11 | 12 | const ( 13 | AckType_8001_Succeed byte = iota 14 | AckType_8001_Failed 15 | AckType_8001_Incorrect 16 | AckType_8001_Unsupport 17 | AckType_8001_AlarmConfirm 18 | ) 19 | 20 | const ( 21 | AckType_8100_Succeed byte = iota 22 | AckType_8100_Vehicle_Registered 23 | AckType_8100_Vehicle_NotFound 24 | AckType_8100_Terminal_Registered 25 | AckType_8100_Terminal_NotFound 26 | ) 27 | 28 | const ( 29 | Version2013 byte = iota 30 | Version2019 31 | ) 32 | 33 | var ErrMesgNotSupport = errors.New("unsupported message") 34 | -------------------------------------------------------------------------------- /internal/generator/message.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "go/parser" 5 | "go/token" 6 | "os" 7 | "path" 8 | ) 9 | 10 | func ParseMesgDecls() ([]*MesgDecl, error) { 11 | var ( 12 | srcPath string 13 | fileSet = token.NewFileSet() 14 | ) 15 | 16 | cwd, err := os.Getwd() 17 | 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | srcPath = path.Join(cwd, "message") 23 | pkgMap, err := parser.ParseDir(fileSet, srcPath, nil, parser.DeclarationErrors) 24 | 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | declNames := parseMesgDeclNames(pkgMap) 30 | mesgDecls, err := buildMesgDecls(declNames) 31 | 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return mesgDecls, nil 37 | } 38 | -------------------------------------------------------------------------------- /tools/generator/decoder/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path" 6 | 7 | . "github.com/dave/jennifer/jen" 8 | "github.com/francistm/jt808-golang/internal/generator" 9 | ) 10 | 11 | func main() { 12 | f := NewFile("message") 13 | f.HeaderComment("Code generated by generator/decoder, DO NOT MODIFY MANUALLY") 14 | mesgDecls, err := generator.ParseMesgDecls() 15 | 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | genMesgBodyIface(f, mesgDecls) 21 | genNewPkgBodyFromMesgId(f, mesgDecls) 22 | 23 | cwd, err := os.Getwd() 24 | 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | filePath := path.Join(cwd, "message", "mesg_pack.gen.go") 30 | 31 | outFile, err := os.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644) 32 | 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | if err := f.Render(outFile); err != nil { 38 | panic(err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/generator/ast.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "go/ast" 5 | "regexp" 6 | ) 7 | 8 | // regexp to match message struct name 9 | // e.g. Body0001, Body0001_11, Body0001_19 10 | var structNameRegex = regexp.MustCompile(`^Body(\d{4})(_(\d{2}))?$`) 11 | 12 | func parseMesgDeclNames(pkgMap map[string]*ast.Package) []string { 13 | declNames := make([]string, 0, 100) 14 | 15 | for _, pkg := range pkgMap { 16 | for _, astFile := range pkg.Files { 17 | if astFile.Scope == nil { 18 | continue 19 | } 20 | 21 | for _, obj := range astFile.Scope.Objects { 22 | if obj.Kind != ast.Typ { 23 | continue 24 | } 25 | 26 | decl := obj.Decl.(*ast.TypeSpec) 27 | 28 | if _, ok := decl.Type.(*ast.StructType); !ok { 29 | continue 30 | } 31 | 32 | if !structNameRegex.MatchString(obj.Name) { 33 | continue 34 | } 35 | 36 | declNames = append(declNames, obj.Name) 37 | } 38 | } 39 | } 40 | 41 | return declNames 42 | } 43 | -------------------------------------------------------------------------------- /internal/tag/tag.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | Name = "jt808" 10 | 11 | EncodingAuto = "auto" 12 | EncodingBCD = "bcd" 13 | EncodingGBK = "gbk" 14 | EncodingRaw = "raw" 15 | ) 16 | 17 | type MesgTag struct { 18 | Length int 19 | Encoding string 20 | } 21 | 22 | func NewMesgTag(s string) (*MesgTag, error) { 23 | t := &MesgTag{ 24 | Length: 0, 25 | Encoding: EncodingAuto, 26 | } 27 | 28 | if s == "" { 29 | return t, nil 30 | } 31 | 32 | var ( 33 | length string 34 | encoding string 35 | ) 36 | 37 | switch strings.Count(s, ",") { 38 | case 0: 39 | length = s 40 | 41 | case 1: 42 | i := strings.IndexByte(s, ',') 43 | 44 | length = s[:i] 45 | encoding = s[i+1:] 46 | } 47 | 48 | if length != "" { 49 | lenInt, err := strconv.ParseInt(length, 10, 8) 50 | 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | t.Length = int(lenInt) 56 | } 57 | 58 | if encoding != "" { 59 | t.Encoding = encoding 60 | } 61 | 62 | return t, nil 63 | } 64 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE= 2 | github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 8 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /internal/bytes/buffer_test.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_Buffer_WriteBCD(t *testing.T) { 10 | type args struct { 11 | s string 12 | size int 13 | } 14 | 15 | tests := []struct { 16 | name string 17 | args args 18 | wantData []byte 19 | wantErr bool 20 | }{ 21 | { 22 | name: "string less than size", 23 | args: args{ 24 | s: "123", 25 | size: 4, 26 | }, 27 | wantData: []byte{0x00, 0x00, 0x01, 0x23}, 28 | wantErr: false, 29 | }, 30 | { 31 | name: "string equal size", 32 | args: args{ 33 | s: "12345678", 34 | size: 4, 35 | }, 36 | wantData: []byte{0x12, 0x34, 0x56, 0x78}, 37 | }, 38 | { 39 | name: "string greater than size", 40 | args: args{ 41 | s: "123456789", 42 | size: 4, 43 | }, 44 | wantData: []byte{0x12, 0x34, 0x56, 0x78}, 45 | }, 46 | } 47 | 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | buffer := NewBuffer() 51 | 52 | err := buffer.WriteBCD(tt.args.s, tt.args.size) 53 | 54 | if tt.wantErr { 55 | assert.Error(t, err) 56 | } else if assert.NoError(t, err) { 57 | assert.Equal(t, tt.wantData, buffer.Bytes()) 58 | } 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /message/0102_19_test.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_Body0102_UnmarshalBinary(t *testing.T) { 10 | data := []byte{0x05, 0x34, 0x35, 0x36, 0x31, 0x32, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x61, 0x62, 0x63, 0x64, 0x65, 0x76, 0x32, 0x2E, 0x30, 0x2E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 11 | body0102 := new(Body0102_19) 12 | 13 | err := body0102.UnmarshalBinary(data) 14 | 15 | if assert.NoError(t, err) { 16 | assert.Equal(t, "45612", body0102.AuthCode) 17 | assert.Equal(t, "1234567890abcde", body0102.DeviceIMEI) 18 | assert.Equal(t, "v2.0.0", body0102.DeviceVersion) 19 | } 20 | } 21 | 22 | func Test_Body0102_MarshalBinary(t *testing.T) { 23 | body0102 := &Body0102_19{ 24 | AuthCode: "45612", 25 | DeviceIMEI: "1234567890abcde", 26 | DeviceVersion: "v2.0.0", 27 | } 28 | 29 | got, err := body0102.MarshalBinary() 30 | 31 | if assert.NoError(t, err) { 32 | assert.Equal(t, []byte{0x05, 0x34, 0x35, 0x36, 0x31, 0x32, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x61, 0x62, 0x63, 0x64, 0x65, 0x76, 0x32, 0x2E, 0x30, 0x2E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, got) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /message/0102_19.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/francistm/jt808-golang/internal/bytes" 7 | ) 8 | 9 | // 终端鉴权 10 | type Body0102_19 struct { 11 | AuthCodeSize uint8 12 | AuthCode string 13 | DeviceIMEI string // 15 byte 14 | DeviceVersion string // 20 byte 15 | } 16 | 17 | func (body *Body0102_19) MarshalBinary() ([]byte, error) { 18 | buf := bytes.NewBuffer() 19 | 20 | buf.WriteUint8(uint8(len(body.AuthCode))) 21 | buf.WriteString(body.AuthCode) 22 | buf.WriteFixedString(body.DeviceIMEI, 15) 23 | buf.WriteFixedString(body.DeviceVersion, 20) 24 | 25 | return buf.Bytes(), nil 26 | } 27 | 28 | func (body *Body0102_19) UnmarshalBinary(data []byte) error { 29 | reader := bytes.NewReader(data) 30 | authSize, err := reader.ReadUint8() 31 | 32 | if err != nil { 33 | return err 34 | } 35 | 36 | authCode, err := reader.ReadFixedString(int(authSize)) 37 | 38 | if err != nil { 39 | return err 40 | } 41 | 42 | imei, err := reader.ReadFixedString(15) 43 | 44 | if err != nil { 45 | return err 46 | } 47 | 48 | version, err := reader.ReadFixedString(20) 49 | 50 | if err != nil { 51 | return err 52 | } 53 | 54 | body.AuthCodeSize = authSize 55 | body.AuthCode = authCode 56 | body.DeviceIMEI = strings.TrimRight(imei, "\x00") 57 | body.DeviceVersion = strings.TrimRight(version, "\x00") 58 | 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /internal/bytes/escape.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | // 消息发送前对消息中 0x7e, 0x7d 进行转义 10 | // 需先将消息体进行转义,然后在首位增加 0x7e 的标识位字节 11 | func Escape(buf []byte) ([]byte, error) { 12 | var writer bytes.Buffer 13 | var reader = bytes.NewReader(buf) 14 | 15 | for { 16 | b, err := reader.ReadByte() 17 | 18 | if errors.Is(err, io.EOF) { 19 | break 20 | } 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | switch b { 27 | case 0x7d: 28 | _, err = writer.Write([]byte{0x7d, 0x01}) 29 | case 0x7e: 30 | _, err = writer.Write([]byte{0x7d, 0x02}) 31 | default: 32 | err = writer.WriteByte(b) 33 | } 34 | 35 | if err != nil { 36 | return nil, err 37 | } 38 | } 39 | 40 | return writer.Bytes(), nil 41 | } 42 | 43 | // 消息收到后,对其中 0x7d01, 0x7d02 进行还原 44 | // 需先去除首位的 0x7e 标识位字节后,再进行消息体转移 45 | func Unescape(buf []byte) ([]byte, error) { 46 | var writer bytes.Buffer 47 | var reader = bytes.NewReader(buf) 48 | 49 | for { 50 | b, err := reader.ReadByte() 51 | 52 | if errors.Is(err, io.EOF) { 53 | break 54 | } 55 | 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | if b != 0x7d { 61 | err := writer.WriteByte(b) 62 | 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | continue 68 | } 69 | 70 | nextByte, err := reader.ReadByte() 71 | 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | switch nextByte { 77 | case 0x01: 78 | writer.WriteByte(0x7d) 79 | case 0x02: 80 | writer.WriteByte(0x7e) 81 | default: 82 | return nil, errors.New("invalid char after 0x7e when unescape") 83 | } 84 | } 85 | 86 | return writer.Bytes(), nil 87 | } 88 | -------------------------------------------------------------------------------- /internal/bytes/buffer.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "errors" 8 | "strings" 9 | ) 10 | 11 | type Buffer struct { 12 | *bytes.Buffer 13 | } 14 | 15 | func NewBuffer() *Buffer { 16 | return &Buffer{ 17 | Buffer: bytes.NewBuffer(nil), 18 | } 19 | } 20 | 21 | func (b *Buffer) WriteUint8(i uint8) error { 22 | return b.WriteByte(i) 23 | } 24 | 25 | func (b *Buffer) WriteUint16(i uint16) error { 26 | data := make([]byte, 2) 27 | 28 | binary.BigEndian.PutUint16(data, i) 29 | 30 | if _, err := b.Write(data); err != nil { 31 | return err 32 | } 33 | 34 | return nil 35 | } 36 | 37 | func (b *Buffer) WriteUint32(i uint32) error { 38 | data := make([]byte, 4) 39 | 40 | binary.BigEndian.PutUint32(data, i) 41 | 42 | if _, err := b.Write(data); err != nil { 43 | return err 44 | } 45 | 46 | return nil 47 | } 48 | 49 | func (b *Buffer) WriteBCD(s string, size int) error { 50 | if size%2 != 0 { 51 | return errors.New("size must be even") 52 | } 53 | 54 | if len(s)%2 != 0 && len(s) < size*2 { 55 | s = "0" + s 56 | } else if len(s)%2 != 0 && len(s) > size*2 { 57 | s = s[:size*2] 58 | } 59 | 60 | data, err := hex.DecodeString(s) 61 | 62 | if err != nil { 63 | return err 64 | } 65 | 66 | if len(data) < size { 67 | data = append(make([]byte, size-len(data)), data...) 68 | } else if len(data) > size { 69 | data = data[:size] 70 | } 71 | 72 | if _, err := b.Write(data); err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func (b *Buffer) WriteFixedString(s string, size int) { 80 | if len(s) < size { 81 | s = s + strings.Repeat("\x00", size-len(s)) 82 | } else if len(s) > size { 83 | s = s[:size] 84 | } 85 | 86 | b.WriteString(s) 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go](https://github.com/francistm/jt808-golang/actions/workflows/go.yml/badge.svg)](https://github.com/francistm/jt808-golang/actions/workflows/go.yml) 2 | 3 | 部分实现和测试用例参考 4 | 5 | Project is WIP 6 | 7 | # Message Def 8 | ``` go 9 | type Body0001 struct { 10 | AckMesgId uint16 11 | AckSerialId uint16 12 | AckType uint8 13 | } 14 | ``` 15 | 结构体字段顺序按照序列化的先后顺序。 16 | 17 | ## Field type mapping 18 | 19 | | Golang Type | Protocl Type | Tag | 20 | | ----------- | ------------ | -------- | 21 | | uint8 | byte | | 22 | | uint16 | word | | 23 | | uint32 | dword | | 24 | | []byte | byte[n] | n,raw | 25 | | string | bcd[n] | n,bcd | 26 | | string | string | n,gbk | 27 | 28 | 29 | ## Complex message unmarshal 30 | 见 [./jt808/message/0200.go](https://github.com/francistm/jt808-golang/blob/c02868ec780de98aa3301ac24308a25532f2a7f6/jt808/message/0200.go) 。 31 | 32 | 先将消息解析为 []byte 类型,然后在结构体中增加方法单独解析。 33 | 34 | ## Package message unmarshal 35 | 如果消息头部中包含分包信息`MessagePack.Package`,则消息正文会被解析为`message.PartialPackBody`。 36 | 37 | 待消息全部接收完成后,使用 `jt808.ConcatUnmarshal(packs []*MessagePack[*message.PartialPackBody], target *MessagePack[T])` 方法一并进行解析。 38 | 39 | 详情见 [./jt808/decoder_test.go:40](https://github.com/francistm/jt808-golang/blob/c02868ec780de98aa3301ac24308a25532f2a7f6/jt808/decoder_test.go#L40) 中的测试用例。 40 | 41 | # Benchmark 42 | Just to see how it works. Don't take it seriously. 43 | 44 | ~~~ 45 | > go version 46 | go version go1.20.10 darwin/arm64 47 | 48 | > sysctl -a | grep machdep.cpu.brand_string 49 | machdep.cpu.brand_string: Apple M1 Pro 50 | 51 | > go test -bench=. -benchmem ./... 52 | Benchmark_Unmarshal0001-8 1186509 1006 ns/op 776 B/op 23 allocs/op 53 | ~~~ 54 | -------------------------------------------------------------------------------- /message/mesg_header_test.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Test_PackHeader_MarshalBinary(t *testing.T) { 10 | packHeader := PackHeader{ 11 | MessageID: 0x0102, 12 | Property: PackHeaderProperty{ 13 | BodyByteLength: 5, 14 | Version: Version2013, 15 | }, 16 | TerminalMobileNum: "013812345678", 17 | SerialNum: 0x0087, 18 | } 19 | 20 | got, err := packHeader.MarshalBinary() 21 | 22 | if assert.NoError(t, err) { 23 | assert.Equal(t, []byte{0x01, 0x02, 0x00, 0x05, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x00, 0x87}, got) 24 | } 25 | } 26 | 27 | func Test_PackHeader_UnmarshalBinary(t *testing.T) { 28 | var packHeader PackHeader 29 | var packHeaderData = []byte{0x01, 0x02, 0x00, 0x05, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x00, 0x87} 30 | 31 | err := packHeader.UnmarshalBinary(packHeaderData) 32 | 33 | if assert.NoError(t, err) { 34 | assert.Equal(t, uint16(0x0102), packHeader.MessageID) 35 | assert.Equal(t, uint16(0x0005), packHeader.Property.BodyByteLength) 36 | assert.Equal(t, false, packHeader.Property.IsEncrypted) 37 | assert.Equal(t, false, packHeader.Property.IsMultiplePackage) 38 | assert.Equal(t, "013812345678", packHeader.TerminalMobileNum) 39 | assert.Equal(t, uint16(0x0087), packHeader.SerialNum) 40 | assert.Nil(t, packHeader.Package) 41 | } 42 | } 43 | 44 | func Test_PackHeader_UnmarshalBinary_MultiPkg(t *testing.T) { 45 | var packHeader PackHeader 46 | var packHeaderData = []byte{0x08, 0x01, 0x23, 0x24, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x31, 0x00, 0x0a, 0x00, 0x01} 47 | 48 | err := packHeader.UnmarshalBinary(packHeaderData) 49 | 50 | if assert.NoError(t, err) { 51 | assert.Equal(t, uint16(0x0801), packHeader.MessageID) 52 | if assert.NotNil(t, packHeader.Package) { 53 | assert.Equal(t, uint16(1), packHeader.Package.Index) 54 | assert.Equal(t, uint16(10), packHeader.Package.Total) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tools/generator/decoder/gen_newpkg_body_from_mesgid.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/dave/jennifer/jen" 5 | "github.com/francistm/jt808-golang/internal/generator" 6 | ) 7 | 8 | func genNewPkgBodyFromMesgId(f *File, mesgDecls []*generator.MesgDecl) { 9 | f.Func(). 10 | Params( 11 | Id("m").Op("*").Id("MessagePack").Types(Id("T")), 12 | ). 13 | Id("NewPackBodyFromMesgId"). 14 | Params(). 15 | Parens(List( 16 | Id("MesgBody"), 17 | Error(), 18 | )). 19 | Block( 20 | Const().Id("unsupportErrMesg").Op("=").Lit("unsupport protocol version: %d"), 21 | Line(), 22 | 23 | If(Id("m").Dot("PackHeader").Dot("Package").Op("!=").Nil()).Block( 24 | Return(New(Id("PartialPackBody")), Nil()), 25 | ).Else().Block( 26 | Switch(Id("m").Dot("PackHeader").Dot("MessageID")).BlockFunc(func(g *Group) { 27 | for _, mesgDecl := range mesgDecls { 28 | g.Case(Lit(mesgDecl.MesgId)).BlockFunc(func(g *Group) { 29 | if mesgDecl.Versions[0] != nil { 30 | g.Return(New(Id(mesgDecl.Versions[0].StructName)), Nil()) 31 | } else { 32 | if mesgDecl.Versions[1] != nil { 33 | g.If(Id("m").Dot("PackHeader").Dot("Property").Dot("Version").Op("==").Id("Version2013")). 34 | Block(Return( 35 | New(Id(mesgDecl.Versions[1].StructName)), 36 | Nil(), 37 | )) 38 | } 39 | 40 | if mesgDecl.Versions[2] != nil { 41 | g.If(Id("m").Dot("PackHeader").Dot("Property").Dot("Version").Op("==").Id("Version2019")). 42 | Block(Return( 43 | New(Id(mesgDecl.Versions[2].StructName)), 44 | Nil(), 45 | )) 46 | } 47 | 48 | g.Return( 49 | Nil(), 50 | Qual("fmt", "Errorf").Call( 51 | Id("unsupportErrMesg"), 52 | Id("m").Dot("PackHeader").Dot("Property").Dot("Version"), 53 | ), 54 | ) 55 | } 56 | }) 57 | } 58 | 59 | g.Default().Return( 60 | Nil(), 61 | Id("ErrMesgNotSupport"), 62 | ) 63 | }), 64 | ), 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /internal/generator/decl.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strconv" 7 | ) 8 | 9 | type MesgDecl struct { 10 | MesgId uint16 11 | Versions [3]*MesgDeclVersion 12 | } 13 | 14 | type MesgDeclVersion struct { 15 | Version int 16 | StructName string 17 | } 18 | 19 | func parseDeclName(structName string) (uint16, int, error) { 20 | var ( 21 | version int 22 | matched = structNameRegex.FindStringSubmatch(structName) 23 | ) 24 | 25 | if len(matched) != 4 { 26 | return 0, 0, fmt.Errorf("invalid name %s", structName) 27 | } 28 | 29 | mesgIdParsed, err := strconv.ParseUint(matched[1], 16, 16) 30 | 31 | if err != nil { 32 | return 0, 0, fmt.Errorf("invalid name %s, %w", structName, err) 33 | } 34 | 35 | if matched[3] != "" { 36 | versionParsed, err := strconv.ParseUint(matched[3], 10, 8) 37 | 38 | if err != nil { 39 | return 0, 0, fmt.Errorf("invalid name %s, %w", structName, err) 40 | } 41 | 42 | version = int(versionParsed) 43 | } 44 | 45 | return uint16(mesgIdParsed), version, nil 46 | } 47 | 48 | func buildMesgDecls(declNames []string) ([]*MesgDecl, error) { 49 | var ( 50 | mesgDecls = make([]*MesgDecl, 0, 100) 51 | mesgDeclsMap = make(map[uint16]*MesgDecl, 100) 52 | ) 53 | 54 | for _, typeName := range declNames { 55 | mesgId, version, err := parseDeclName(typeName) 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | mesgDecl, ok := mesgDeclsMap[mesgId] 62 | 63 | if !ok { 64 | mesgDecl = &MesgDecl{MesgId: mesgId} 65 | 66 | mesgDeclsMap[mesgId] = mesgDecl 67 | mesgDecls = append(mesgDecls, mesgDecl) 68 | } 69 | 70 | declVersion := &MesgDeclVersion{ 71 | Version: version, 72 | StructName: typeName, 73 | } 74 | 75 | switch version { 76 | case 13: 77 | mesgDecl.Versions[1] = declVersion 78 | 79 | case 19: 80 | mesgDecl.Versions[2] = declVersion 81 | 82 | default: 83 | mesgDecl.Versions[0] = declVersion 84 | } 85 | } 86 | 87 | sort.Slice(mesgDecls, func(i, j int) bool { 88 | return mesgDecls[i].MesgId < mesgDecls[j].MesgId 89 | }) 90 | 91 | return mesgDecls, nil 92 | } 93 | -------------------------------------------------------------------------------- /internal/bytes/reader.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | type Reader struct { 11 | *bytes.Reader 12 | } 13 | 14 | func NewReader(in []byte) *Reader { 15 | return &Reader{ 16 | Reader: bytes.NewReader(in), 17 | } 18 | } 19 | 20 | func (r *Reader) ReadUint8() (uint8, error) { 21 | buf := make([]byte, 1) 22 | 23 | if _, err := r.Read(buf); err != nil { 24 | return 0, err 25 | } 26 | 27 | return buf[0], nil 28 | } 29 | 30 | func (r *Reader) ReadUint16() (uint16, error) { 31 | buf := make([]byte, 2) 32 | 33 | if _, err := r.Read(buf); err != nil { 34 | return 0, err 35 | } 36 | 37 | return binary.BigEndian.Uint16(buf), nil 38 | } 39 | 40 | func (r *Reader) ReadUint32() (uint32, error) { 41 | buf := make([]byte, 4) 42 | 43 | if _, err := r.Read(buf); err != nil { 44 | return 0, err 45 | } 46 | 47 | return binary.BigEndian.Uint32(buf), nil 48 | } 49 | 50 | func (r *Reader) ReadFixedBytes(size int) ([]byte, error) { 51 | buf := make([]byte, size) 52 | 53 | n, err := r.Read(buf) 54 | 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return buf[0:n], nil 60 | } 61 | 62 | func (r *Reader) ReadBCD(size int) (string, error) { 63 | buf := make([]byte, size) 64 | 65 | if _, err := r.Read(buf); err != nil { 66 | return "", err 67 | } 68 | 69 | return fmt.Sprintf("%x", buf), nil 70 | } 71 | 72 | func (r *Reader) ReadFixedString(size int) (string, error) { 73 | buf := make([]byte, size) 74 | readSize, err := r.Read(buf) 75 | 76 | if err != nil { 77 | return "", err 78 | } 79 | 80 | return string(buf[0:readSize]), nil 81 | } 82 | 83 | func (r *Reader) ReadRestAsString() (string, error) { 84 | data, err := io.ReadAll(r) 85 | 86 | if err != nil { 87 | return "", err 88 | } 89 | 90 | return string(data), nil 91 | } 92 | 93 | func (r *Reader) UnreadFixedBytes(size int) error { 94 | for i := 0; i < size; i++ { 95 | if err := r.UnreadByte(); err != nil { 96 | return err 97 | } 98 | } 99 | 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /internal/encode/encode.go: -------------------------------------------------------------------------------- 1 | package encode 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | "reflect" 7 | 8 | "github.com/francistm/jt808-golang/internal/bytes" 9 | "github.com/francistm/jt808-golang/internal/tag" 10 | ) 11 | 12 | func MarshalStruct(buffer *bytes.Buffer, source any) error { 13 | if marshaller, ok := source.(encoding.BinaryMarshaler); ok { 14 | data, err := marshaller.MarshalBinary() 15 | 16 | if err != nil { 17 | return err 18 | } 19 | 20 | buffer.Write(data) 21 | } 22 | 23 | mesgBodyTypeRef := reflect.TypeOf(source) 24 | mesgBodyValueRef := reflect.ValueOf(source) 25 | 26 | if mesgBodyTypeRef.Kind() == reflect.Ptr { 27 | mesgBodyTypeRef = mesgBodyTypeRef.Elem() 28 | mesgBodyValueRef = mesgBodyValueRef.Elem() 29 | } 30 | 31 | for i := 0; i < mesgBodyValueRef.NumField(); i++ { 32 | fieldType := mesgBodyTypeRef.Field(i) 33 | fieldValue := mesgBodyValueRef.Field(i) 34 | 35 | if fieldType.PkgPath != "" { 36 | continue 37 | } 38 | 39 | rawTag := fieldType.Tag.Get(tag.Name) 40 | parsedTag, err := tag.NewMesgTag(rawTag) 41 | 42 | if err != nil { 43 | return fmt.Errorf("cannot parse tag of field %s.%s", mesgBodyTypeRef.Name(), fieldType.Name) 44 | } 45 | 46 | switch { 47 | case fieldType.Type.Kind() == reflect.Uint8: 48 | err = buffer.WriteUint8(uint8(fieldValue.Uint())) 49 | 50 | case fieldType.Type.Kind() == reflect.Uint16: 51 | err = buffer.WriteUint16(uint16(fieldValue.Uint())) 52 | 53 | case fieldType.Type.Kind() == reflect.Uint32: 54 | err = buffer.WriteUint32(uint32(fieldValue.Uint())) 55 | 56 | case fieldType.Type.Kind() == reflect.Slice && fieldType.Type.Elem().Kind() == reflect.Uint8: 57 | if parsedTag.Encoding != tag.EncodingRaw { 58 | return fmt.Errorf("unsupport field %s.%s encoding: %s", mesgBodyTypeRef.Name(), fieldType.Name, parsedTag.Encoding) 59 | } 60 | 61 | _, err = buffer.Write(fieldValue.Bytes()) 62 | 63 | case fieldType.Type.Kind() == reflect.String: 64 | switch parsedTag.Encoding { 65 | case tag.EncodingBCD: 66 | err = buffer.WriteBCD(fieldValue.String(), parsedTag.Length) 67 | 68 | default: 69 | _, err = buffer.WriteString(fieldValue.String()) 70 | } 71 | 72 | case fieldType.Type.Kind() == reflect.Struct: 73 | err = MarshalStruct(buffer, fieldValue.Interface()) 74 | } 75 | 76 | if err != nil { 77 | return err 78 | } 79 | } 80 | 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /decoder.go: -------------------------------------------------------------------------------- 1 | package jt808 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/francistm/jt808-golang/internal/bytes" 8 | "github.com/francistm/jt808-golang/internal/decode" 9 | "github.com/francistm/jt808-golang/message" 10 | ) 11 | 12 | //go:generate go run github.com/francistm/jt808-golang/tools/generator/decoder 13 | 14 | // Unmarshal 由二进制解析一个完整的消息包 15 | func Unmarshal[T message.MesgBody](data []byte, mesgPack *message.MessagePack[T]) error { 16 | return mesgPack.UnmarshalBinary(data) 17 | } 18 | 19 | // ConcatUnmarshal 拼接多个分段消息并解析 20 | func ConcatUnmarshal[T message.MesgBody](packs []*message.MessagePack[*message.PartialPackBody], pack *message.MessagePack[T]) error { 21 | if len(packs) < 2 { 22 | return ErrIncompletedPkgMesg 23 | } 24 | 25 | if packs[0].PackHeader.Package == nil { 26 | return ErrNotPkgMesg 27 | } 28 | 29 | var ( 30 | mesgBodyBuf = bytes.NewBuffer() 31 | mesgBodyReader *bytes.Reader 32 | mesgId = packs[0].PackHeader.MessageID 33 | ) 34 | 35 | if len(packs) != int(packs[0].PackHeader.Package.Total) { 36 | return ErrIncompletedPkgMesg 37 | } 38 | 39 | sort.Slice(packs, func(i, j int) bool { 40 | var ( 41 | packsLeft = packs[i] 42 | packsRight = packs[j] 43 | ) 44 | 45 | if packsLeft.PackHeader.Package == nil { 46 | return false 47 | } 48 | 49 | return packsLeft.PackHeader.Package.Index < packsRight.PackHeader.Package.Index 50 | }) 51 | 52 | for i, pack := range packs { 53 | if pack.PackHeader.Package == nil { 54 | return ErrNotPkgMesg 55 | } 56 | 57 | if pack.PackHeader.MessageID != mesgId { 58 | return fmt.Errorf("message at %d is not type of %.4X", i+1, mesgId) 59 | } 60 | 61 | if pack.PackHeader.Package.Index != uint16(i+1) { 62 | return fmt.Errorf("message at %d is not the %dth message", i+1, i+1) 63 | } 64 | 65 | mesgId = pack.PackHeader.MessageID 66 | mesgBodyBuf.Write(pack.PackBody.RawBody) 67 | } 68 | 69 | pack.PackHeader = packs[0].PackHeader 70 | pack.PackHeader.Package = nil 71 | pack.PackHeader.Property.IsMultiplePackage = false 72 | pack.PackHeader.Property.BodyByteLength = uint16(mesgBodyBuf.Len()) 73 | 74 | packBody, err := pack.NewPackBodyFromMesgId() 75 | 76 | if err != nil { 77 | return err 78 | } 79 | 80 | typedPackBody, ok := packBody.(T) 81 | 82 | if !ok { 83 | return fmt.Errorf("can't convert body from %T to %T", packBody, pack.PackBody) 84 | } 85 | 86 | pack.Checksum = 0 87 | pack.PackBody = typedPackBody 88 | mesgBodyReader = bytes.NewReader(mesgBodyBuf.Bytes()) 89 | 90 | if err := decode.UnmarshalStruct(mesgBodyReader, packBody); err != nil { 91 | return err 92 | } 93 | 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /encoder_test.go: -------------------------------------------------------------------------------- 1 | package jt808 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/francistm/jt808-golang/message" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestMarshal0200(t *testing.T) { 12 | type args struct { 13 | mesgPack *message.MessagePack[message.MesgBody] 14 | } 15 | 16 | tests := []struct { 17 | name string 18 | args args 19 | want []byte 20 | wantErr bool 21 | }{ 22 | { 23 | name: "mesg 8100", 24 | args: args{ 25 | mesgPack: &message.MessagePack[message.MesgBody]{ 26 | PackHeader: message.PackHeader{ 27 | MessageID: 0x8100, 28 | TerminalMobileNum: "123456789012", 29 | SerialNum: 126, 30 | }, 31 | PackBody: &message.Body8100{ 32 | SerialId: 0x1234, 33 | Result: message.AckType_8100_Succeed, 34 | AuthCode: "123456", 35 | }, 36 | }, 37 | }, 38 | want: []byte{0x7e, 0x81, 0x0, 0x0, 0x9, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x0, 0x7d, 0x2, 0x12, 0x34, 0x0, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x5d, 0x7e}, 39 | wantErr: false, 40 | }, 41 | { 42 | name: "mesg 0200", 43 | args: args{ 44 | mesgPack: &message.MessagePack[message.MesgBody]{ 45 | PackHeader: message.PackHeader{ 46 | MessageID: 0x0200, 47 | TerminalMobileNum: "123456789012", 48 | SerialNum: 126, 49 | }, 50 | PackBody: func() *message.Body0200 { 51 | base := message.Body0200{ 52 | Body0200Base: message.Body0200Base{ 53 | WarnFlag: 1, 54 | StatusFlag: 2, 55 | Altitude: 40, 56 | Direction: 0, 57 | }, 58 | } 59 | 60 | base.SetLatitude(12.222222) 61 | base.SetLongitude(132.444444) 62 | base.SetSpeed(6) 63 | base.SetTime(time.Unix(1539569410, 0)) // // 2018-10-15 10:10:10 UTC 64 | base.SetExtraMessage(map[uint8][]byte{ 65 | 0x01: {0x00, 0x00, 0x00, 0x64}, 66 | 0x02: {0x00, 0x7d}, 67 | }) 68 | 69 | return &base 70 | }(), 71 | }, 72 | }, 73 | want: []byte{0x7e, 0x02, 0x00, 0x00, 0x26, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x00, 0x7d, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0xba, 0x7f, 0x0e, 0x07, 0xe4, 0xf1, 0x1c, 0x00, 0x28, 0x00, 0x3c, 0x00, 0x00, 0x18, 0x10, 0x15, 0x10, 0x10, 0x10, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x02, 0x02, 0x00, 0x7d, 0x01, 0x13, 0x7e}, 74 | wantErr: false, 75 | }, 76 | } 77 | 78 | for _, tt := range tests { 79 | t.Run(tt.name, func(t *testing.T) { 80 | assert.NotPanics(t, func() { 81 | got, err := Marshal(tt.args.mesgPack) 82 | 83 | if tt.wantErr { 84 | assert.Error(t, err) 85 | } else if assert.NoError(t, err) { 86 | assert.Equal(t, tt.want, got) 87 | } 88 | }) 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /message/0200.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "math" 7 | "sort" 8 | "time" 9 | 10 | "github.com/francistm/jt808-golang/internal/bytes" 11 | ) 12 | 13 | // 位置信息汇报 14 | type Body0200 struct { 15 | Body0200Base 16 | RawExtraMessage []byte `jt808:"-1,raw"` 17 | 18 | parsedExtraMessage map[uint8][]byte // cache parsed rawExtraMessage 19 | } 20 | 21 | type Body0200Base struct { 22 | WarnFlag uint32 23 | StatusFlag uint32 24 | RawLatitude uint32 25 | RawLongitude uint32 26 | Altitude uint16 27 | RawSpeed uint16 28 | Direction uint16 29 | RawTime string `jt808:"6,bcd"` 30 | } 31 | 32 | func (b *Body0200) Latitude() float64 { 33 | return float64(b.RawLatitude) / math.Pow(10, 6) 34 | } 35 | 36 | func (b *Body0200) SetLatitude(f float64) { 37 | b.RawLatitude = uint32(f * math.Pow(10, 6)) 38 | } 39 | 40 | func (b *Body0200) Longitude() float64 { 41 | return float64(b.RawLongitude) / math.Pow(10, 6) 42 | } 43 | 44 | func (b *Body0200) SetLongitude(f float64) { 45 | b.RawLongitude = uint32(f * math.Pow(10, 6)) 46 | } 47 | 48 | func (b *Body0200) Speed() float32 { 49 | return float32(b.RawSpeed) / 10 50 | } 51 | 52 | func (b *Body0200) SetSpeed(f float32) { 53 | b.RawSpeed = uint16(f * 10) 54 | } 55 | 56 | func (b *Body0200) Time() time.Time { 57 | t, _ := time.ParseInLocation(timeLayoutBCD, b.RawTime, timezoneCST) 58 | 59 | return t 60 | } 61 | 62 | func (b *Body0200) SetTime(tIn time.Time) { 63 | b.RawTime = tIn.In(timezoneCST).Format(timeLayoutBCD) 64 | } 65 | 66 | func (b *Body0200) ExtraMessage() (map[uint8][]byte, error) { 67 | if len(b.RawExtraMessage) == 0 { 68 | return nil, nil 69 | } 70 | 71 | if b.parsedExtraMessage == nil { 72 | reader := bytes.NewReader(b.RawExtraMessage) 73 | extraMesgs := make(map[uint8][]byte) 74 | 75 | for { 76 | id, err := reader.ReadByte() 77 | 78 | if errors.Is(err, io.EOF) { 79 | break 80 | } 81 | 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | dataSize, err := reader.ReadByte() 87 | 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | mesgData, err := reader.ReadFixedBytes(int(dataSize)) 93 | 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | extraMesgs[id] = mesgData 99 | } 100 | 101 | b.parsedExtraMessage = extraMesgs 102 | } 103 | 104 | return b.parsedExtraMessage, nil 105 | } 106 | 107 | func (b *Body0200) SetExtraMessage(m map[uint8][]byte) error { 108 | var ( 109 | writer = bytes.NewBuffer() 110 | extraMesgIds = make([]uint8, 0, len(m)) 111 | ) 112 | 113 | for extraMesgId := range m { 114 | extraMesgIds = append(extraMesgIds, extraMesgId) 115 | } 116 | 117 | sort.Slice(extraMesgIds, func(i, j int) bool { 118 | return extraMesgIds[i] < extraMesgIds[j] 119 | }) 120 | 121 | for _, extraMesgId := range extraMesgIds { 122 | data := m[extraMesgId] 123 | dataSize := uint8(len(data)) 124 | 125 | if err := writer.WriteByte(extraMesgId); err != nil { 126 | return err 127 | } 128 | 129 | if err := writer.WriteByte(dataSize); err != nil { 130 | return err 131 | } 132 | 133 | if _, err := writer.Write(data); err != nil { 134 | return err 135 | } 136 | } 137 | 138 | // update cache 139 | b.parsedExtraMessage = m 140 | b.RawExtraMessage = writer.Bytes() 141 | 142 | return nil 143 | } 144 | -------------------------------------------------------------------------------- /message/0104_19.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import "github.com/francistm/jt808-golang/internal/bytes" 4 | 5 | type Body0104_19 struct { 6 | // 终端类型 7 | TerminalKind byte 8 | 9 | // 制造商ID 10 | // 5位 11 | ManufacturerId []byte 12 | 13 | // 终端型号 14 | // 30位 15 | TerminalModel []byte 16 | 17 | // 终端ID 18 | // 30位 19 | TerminalId []byte 20 | 21 | // 终端SIM卡ICCID 22 | // 10位 BCD 23 | TerminalIccid string 24 | 25 | // 终端硬件版本号长度 26 | TerminalHardwareVersionLength byte 27 | 28 | // 终端硬件版本号 29 | TerminalHardwareVersion []byte 30 | 31 | // 终端固件版本号长度 32 | TerminalFirmwareVersionLength byte 33 | 34 | // 终端固件版本号 35 | TerminalFirmwareVersion []byte 36 | 37 | // GNSS模块属性 38 | GnssModuleProperty byte 39 | 40 | // 通信模块属性 41 | CommunicationModuleProperty byte 42 | } 43 | 44 | func (b *Body0104_19) MarshalBinary() ([]byte, error) { 45 | buffer := bytes.NewBuffer() 46 | 47 | buffer.WriteByte(b.TerminalKind) 48 | buffer.Write(b.ManufacturerId) 49 | buffer.Write(b.TerminalModel) 50 | buffer.Write(b.TerminalId) 51 | buffer.WriteBCD(b.TerminalIccid, 10) 52 | buffer.WriteByte(uint8(len(b.TerminalHardwareVersion))) 53 | buffer.Write(b.TerminalHardwareVersion) 54 | buffer.WriteByte(uint8(len(b.TerminalFirmwareVersion))) 55 | buffer.Write(b.TerminalFirmwareVersion) 56 | buffer.WriteByte(b.GnssModuleProperty) 57 | buffer.WriteByte(b.CommunicationModuleProperty) 58 | 59 | return buffer.Bytes(), nil 60 | } 61 | 62 | func (b *Body0104_19) UnmarshalBinary(data []byte) error { 63 | reader := bytes.NewReader(data) 64 | 65 | terminalKind, err := reader.ReadByte() 66 | 67 | if err != nil { 68 | return err 69 | } 70 | 71 | manufacturerId, err := reader.ReadFixedBytes(5) 72 | 73 | if err != nil { 74 | return err 75 | } 76 | 77 | terminalModel, err := reader.ReadFixedBytes(30) 78 | 79 | if err != nil { 80 | return err 81 | } 82 | 83 | terminalId, err := reader.ReadFixedBytes(30) 84 | 85 | if err != nil { 86 | return err 87 | } 88 | 89 | terminalIccid, err := reader.ReadBCD(10) 90 | 91 | if err != nil { 92 | return err 93 | } 94 | 95 | terminalHardwareVersionLength, err := reader.ReadByte() 96 | 97 | if err != nil { 98 | return err 99 | } 100 | 101 | terminalHardwareVersion, err := reader.ReadFixedBytes(int(terminalHardwareVersionLength)) 102 | 103 | if err != nil { 104 | return err 105 | } 106 | 107 | terminalFirmwareVersionLength, err := reader.ReadByte() 108 | 109 | if err != nil { 110 | return err 111 | } 112 | 113 | terminalFirmwareVersion, err := reader.ReadFixedBytes(int(terminalFirmwareVersionLength)) 114 | 115 | if err != nil { 116 | return err 117 | } 118 | 119 | gnssModuleProperty, err := reader.ReadByte() 120 | 121 | if err != nil { 122 | return err 123 | } 124 | 125 | communicationModuleProperty, err := reader.ReadByte() 126 | 127 | if err != nil { 128 | return err 129 | } 130 | 131 | b.TerminalKind = terminalKind 132 | b.ManufacturerId = manufacturerId 133 | b.TerminalModel = terminalModel 134 | b.TerminalId = terminalId 135 | b.TerminalIccid = terminalIccid 136 | b.TerminalHardwareVersionLength = terminalHardwareVersionLength 137 | b.TerminalHardwareVersion = terminalHardwareVersion 138 | b.TerminalFirmwareVersionLength = terminalFirmwareVersionLength 139 | b.TerminalFirmwareVersion = terminalFirmwareVersion 140 | b.GnssModuleProperty = gnssModuleProperty 141 | b.CommunicationModuleProperty = communicationModuleProperty 142 | 143 | return nil 144 | } 145 | -------------------------------------------------------------------------------- /message/0104_13.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import "github.com/francistm/jt808-golang/internal/bytes" 4 | 5 | // 查询终端属性应答 6 | type Body0107_13 struct { 7 | // 终端类型 8 | TerminalKind byte 9 | 10 | // 制造商ID 11 | // 5位 12 | ManufacturerId []byte 13 | 14 | // 终端型号 15 | // 20位 16 | TerminalModel []byte 17 | 18 | // 终端ID 19 | // 7位 20 | TerminalId []byte 21 | 22 | // 终端SIM卡ICCID 23 | // 10位 BCD 24 | TerminalIccid string 25 | 26 | // 终端硬件版本号长度 27 | TerminalHardwareVersionLength byte 28 | 29 | // 终端硬件版本号 30 | TerminalHardwareVersion []byte 31 | 32 | // 终端固件版本号长度 33 | TerminalFirmwareVersionLength byte 34 | 35 | // 终端固件版本号 36 | TerminalFirmwareVersion []byte 37 | 38 | // GNSS模块属性 39 | GnssModuleProperty byte 40 | 41 | // 通信模块属性 42 | CommunicationModuleProperty byte 43 | } 44 | 45 | func (v *Body0107_13) MarshalBinary() ([]byte, error) { 46 | buffer := bytes.NewBuffer() 47 | 48 | buffer.WriteByte(v.TerminalKind) 49 | buffer.Write(v.ManufacturerId) 50 | buffer.Write(v.TerminalModel) 51 | buffer.Write(v.TerminalId) 52 | buffer.WriteBCD(v.TerminalIccid, 10) 53 | buffer.WriteByte(uint8(len(v.TerminalHardwareVersion))) 54 | buffer.Write(v.TerminalHardwareVersion) 55 | buffer.WriteByte(uint8(len(v.TerminalFirmwareVersion))) 56 | buffer.Write(v.TerminalFirmwareVersion) 57 | buffer.WriteByte(v.GnssModuleProperty) 58 | buffer.WriteByte(v.CommunicationModuleProperty) 59 | 60 | return buffer.Bytes(), nil 61 | } 62 | 63 | func (v *Body0107_13) UnmarshalBinary(data []byte) error { 64 | reader := bytes.NewReader(data) 65 | 66 | terminalKind, err := reader.ReadByte() 67 | 68 | if err != nil { 69 | return err 70 | } 71 | 72 | manufacturerId, err := reader.ReadFixedBytes(5) 73 | 74 | if err != nil { 75 | return err 76 | } 77 | 78 | terminalModel, err := reader.ReadFixedBytes(20) 79 | 80 | if err != nil { 81 | return err 82 | } 83 | 84 | terminalId, err := reader.ReadFixedBytes(7) 85 | 86 | if err != nil { 87 | return err 88 | } 89 | 90 | terminalIccid, err := reader.ReadBCD(10) 91 | 92 | if err != nil { 93 | return err 94 | } 95 | 96 | terminalHardwareVersionLength, err := reader.ReadByte() 97 | 98 | if err != nil { 99 | return err 100 | } 101 | 102 | terminalHardwareVersion, err := reader.ReadFixedBytes(int(terminalHardwareVersionLength)) 103 | 104 | if err != nil { 105 | return err 106 | } 107 | 108 | terminalFirmwareVersionLength, err := reader.ReadByte() 109 | 110 | if err != nil { 111 | return err 112 | } 113 | 114 | terminalFirmwareVersion, err := reader.ReadFixedBytes(int(terminalFirmwareVersionLength)) 115 | 116 | if err != nil { 117 | return err 118 | } 119 | 120 | gnssModuleProperty, err := reader.ReadByte() 121 | 122 | if err != nil { 123 | return err 124 | } 125 | 126 | communicationModuleProperty, err := reader.ReadByte() 127 | 128 | if err != nil { 129 | return err 130 | } 131 | 132 | v.TerminalKind = terminalKind 133 | v.ManufacturerId = manufacturerId 134 | v.TerminalModel = terminalModel 135 | v.TerminalId = terminalId 136 | v.TerminalIccid = terminalIccid 137 | v.TerminalHardwareVersionLength = terminalHardwareVersionLength 138 | v.TerminalHardwareVersion = terminalHardwareVersion 139 | v.TerminalFirmwareVersionLength = terminalFirmwareVersionLength 140 | v.TerminalFirmwareVersion = terminalFirmwareVersion 141 | v.GnssModuleProperty = gnssModuleProperty 142 | v.CommunicationModuleProperty = communicationModuleProperty 143 | 144 | return nil 145 | } 146 | -------------------------------------------------------------------------------- /message/mesg_pack.gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by generator/decoder, DO NOT MODIFY MANUALLY 2 | 3 | package message 4 | 5 | import "fmt" 6 | 7 | func (*Body0001) MesgId() uint16 { 8 | return uint16(0x1) 9 | } 10 | 11 | func (*Body0002) MesgId() uint16 { 12 | return uint16(0x2) 13 | } 14 | 15 | func (*Body0003) MesgId() uint16 { 16 | return uint16(0x3) 17 | } 18 | 19 | func (*Body0004) MesgId() uint16 { 20 | return uint16(0x4) 21 | } 22 | 23 | func (*Body0005) MesgId() uint16 { 24 | return uint16(0x5) 25 | } 26 | 27 | func (*Body0100_13) MesgId() uint16 { 28 | return uint16(0x100) 29 | } 30 | 31 | func (*Body0100_19) MesgId() uint16 { 32 | return uint16(0x100) 33 | } 34 | 35 | func (*Body0102_13) MesgId() uint16 { 36 | return uint16(0x102) 37 | } 38 | 39 | func (*Body0102_19) MesgId() uint16 { 40 | return uint16(0x102) 41 | } 42 | 43 | func (*Body0104_19) MesgId() uint16 { 44 | return uint16(0x104) 45 | } 46 | 47 | func (*Body0107_13) MesgId() uint16 { 48 | return uint16(0x107) 49 | } 50 | 51 | func (*Body0200) MesgId() uint16 { 52 | return uint16(0x200) 53 | } 54 | 55 | func (*Body0801) MesgId() uint16 { 56 | return uint16(0x801) 57 | } 58 | 59 | func (*Body8001) MesgId() uint16 { 60 | return uint16(0x8001) 61 | } 62 | 63 | func (*Body8003) MesgId() uint16 { 64 | return uint16(0x8003) 65 | } 66 | 67 | func (*Body8004) MesgId() uint16 { 68 | return uint16(0x8004) 69 | } 70 | 71 | func (*Body8100) MesgId() uint16 { 72 | return uint16(0x8100) 73 | } 74 | 75 | func (m *MessagePack[T]) NewPackBodyFromMesgId() (MesgBody, error) { 76 | const unsupportErrMesg = "unsupport protocol version: %d" 77 | 78 | if m.PackHeader.Package != nil { 79 | return new(PartialPackBody), nil 80 | } else { 81 | switch m.PackHeader.MessageID { 82 | case uint16(0x1): 83 | return new(Body0001), nil 84 | case uint16(0x2): 85 | return new(Body0002), nil 86 | case uint16(0x3): 87 | return new(Body0003), nil 88 | case uint16(0x4): 89 | return new(Body0004), nil 90 | case uint16(0x5): 91 | return new(Body0005), nil 92 | case uint16(0x100): 93 | if m.PackHeader.Property.Version == Version2013 { 94 | return new(Body0100_13), nil 95 | } 96 | if m.PackHeader.Property.Version == Version2019 { 97 | return new(Body0100_19), nil 98 | } 99 | return nil, fmt.Errorf(unsupportErrMesg, m.PackHeader.Property.Version) 100 | case uint16(0x102): 101 | if m.PackHeader.Property.Version == Version2013 { 102 | return new(Body0102_13), nil 103 | } 104 | if m.PackHeader.Property.Version == Version2019 { 105 | return new(Body0102_19), nil 106 | } 107 | return nil, fmt.Errorf(unsupportErrMesg, m.PackHeader.Property.Version) 108 | case uint16(0x104): 109 | if m.PackHeader.Property.Version == Version2019 { 110 | return new(Body0104_19), nil 111 | } 112 | return nil, fmt.Errorf(unsupportErrMesg, m.PackHeader.Property.Version) 113 | case uint16(0x107): 114 | if m.PackHeader.Property.Version == Version2013 { 115 | return new(Body0107_13), nil 116 | } 117 | return nil, fmt.Errorf(unsupportErrMesg, m.PackHeader.Property.Version) 118 | case uint16(0x200): 119 | return new(Body0200), nil 120 | case uint16(0x801): 121 | return new(Body0801), nil 122 | case uint16(0x8001): 123 | return new(Body8001), nil 124 | case uint16(0x8003): 125 | return new(Body8003), nil 126 | case uint16(0x8004): 127 | return new(Body8004), nil 128 | case uint16(0x8100): 129 | return new(Body8100), nil 130 | default: 131 | return nil, ErrMesgNotSupport 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /message/mesg_pack.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/francistm/jt808-golang/internal" 7 | "github.com/francistm/jt808-golang/internal/bytes" 8 | "github.com/francistm/jt808-golang/internal/decode" 9 | "github.com/francistm/jt808-golang/internal/encode" 10 | ) 11 | 12 | // MessagePack 通用的消息体结构 13 | type MessagePack[T MesgBody] struct { 14 | PackBody T 15 | PackHeader PackHeader 16 | Checksum uint8 17 | 18 | checksumActually uint8 19 | } 20 | 21 | func (p *MessagePack[T]) MarshalBinary() ([]byte, error) { 22 | var ( 23 | buf = bytes.NewBuffer() 24 | bodyBuf = bytes.NewBuffer() 25 | finalBuf = bytes.NewBuffer() 26 | ) 27 | 28 | err := encode.MarshalStruct(bodyBuf, p.PackBody) 29 | 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | p.PackHeader.Property.BodyByteLength = uint16(bodyBuf.Len()) 35 | 36 | packHeaderData, err := p.PackHeader.MarshalBinary() 37 | 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | if _, err := buf.Write(packHeaderData); err != nil { 43 | return nil, err 44 | } 45 | 46 | if _, err := buf.Write(bodyBuf.Bytes()); err != nil { 47 | return nil, err 48 | } 49 | 50 | checksum, err := bytes.CalcChecksum(buf.Bytes()) 51 | 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | if err := buf.WriteByte(checksum); err != nil { 57 | return nil, err 58 | } 59 | 60 | escapedBytes, err := bytes.Escape(buf.Bytes()) 61 | 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | finalBuf.WriteByte(internal.IdentifyByte) 67 | 68 | if _, err := finalBuf.Write(escapedBytes); err != nil { 69 | return nil, err 70 | } 71 | 72 | finalBuf.WriteByte(internal.IdentifyByte) 73 | 74 | return finalBuf.Bytes(), nil 75 | } 76 | 77 | func (p *MessagePack[T]) UnmarshalBinary(buf []byte) error { 78 | var ( 79 | reader *bytes.Reader 80 | packBodyReader *bytes.Reader 81 | checksumActually byte 82 | ) 83 | 84 | if buf[0] != internal.IdentifyByte { 85 | return fmt.Errorf("invalid prefix byte 0x%.2X", buf[0]) 86 | } 87 | 88 | if buf[len(buf)-1] != internal.IdentifyByte { 89 | return fmt.Errorf("invalid suffix byte 0x%.2X", buf[len(buf)-1]) 90 | } 91 | 92 | buf = buf[1 : len(buf)-1] 93 | buf, err := bytes.Unescape(buf) 94 | 95 | if err != nil { 96 | return err 97 | } 98 | 99 | reader = bytes.NewReader(buf) 100 | 101 | // read header, depends on is multiple package message 102 | // 2011: 12 or 12 + 4 bytes 103 | // 2019: 17 or 17 + 4 bytes 104 | packHeaderData, err := reader.ReadFixedBytes(21) 105 | 106 | if err != nil { 107 | return err 108 | } 109 | 110 | if err := p.PackHeader.UnmarshalBinary(packHeaderData); err != nil { 111 | return err 112 | } 113 | 114 | if p.PackHeader.Property.Version == Version2013 { 115 | _ = reader.UnreadFixedBytes(5 - 21 + len(packHeaderData)) 116 | } 117 | 118 | if !p.PackHeader.Property.IsMultiplePackage { 119 | _ = reader.UnreadFixedBytes(4) 120 | } 121 | 122 | checksumActually, err = bytes.CalcChecksum(buf[0 : len(buf)-1]) 123 | 124 | if err != nil { 125 | return err 126 | } 127 | 128 | p.Checksum = buf[len(buf)-1] 129 | p.checksumActually = checksumActually 130 | 131 | // update PackBody field from readed bytes to struct 132 | packBody, err := p.NewPackBodyFromMesgId() 133 | 134 | if err != nil { 135 | return err 136 | } 137 | 138 | // read bytes according header body data length 139 | packBodyData, err := reader.ReadFixedBytes(int(p.PackHeader.Property.BodyByteLength)) 140 | 141 | if err != nil { 142 | return err 143 | } 144 | 145 | packBodyReader = bytes.NewReader(packBodyData) 146 | 147 | if err := decode.UnmarshalStruct(packBodyReader, packBody); err != nil { 148 | return err 149 | } 150 | 151 | typedPackBody, ok := packBody.(T) 152 | 153 | if !ok { 154 | return fmt.Errorf("can't convert mesgBody %T as %T", packBody, p.PackBody) 155 | } 156 | 157 | p.PackBody = typedPackBody 158 | 159 | return nil 160 | } 161 | 162 | func (p *MessagePack[T]) ChecksumValid() bool { 163 | return p.Checksum == p.checksumActually 164 | } 165 | -------------------------------------------------------------------------------- /internal/decode/decode.go: -------------------------------------------------------------------------------- 1 | package decode 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | "io" 7 | "reflect" 8 | 9 | "github.com/francistm/jt808-golang/internal/bytes" 10 | "github.com/francistm/jt808-golang/internal/tag" 11 | ) 12 | 13 | type RefField struct { 14 | StructName string 15 | FieldName string 16 | Tag string 17 | TypeRef reflect.Type 18 | ValueRef reflect.Value 19 | } 20 | 21 | func UnmarshalStruct(reader *bytes.Reader, target any) error { 22 | if unmarshaller, ok := target.(encoding.BinaryUnmarshaler); ok { 23 | data, err := io.ReadAll(reader) 24 | 25 | if err != nil { 26 | return err 27 | } 28 | 29 | return unmarshaller.UnmarshalBinary(data) 30 | } 31 | 32 | refFields := make([]*RefField, 0, 50) 33 | refFields = append(refFields, &RefField{ 34 | TypeRef: reflect.TypeOf(target), 35 | ValueRef: reflect.ValueOf(target), 36 | }) 37 | 38 | for len(refFields) > 0 { 39 | head := refFields[0] 40 | refFields = refFields[1:] 41 | 42 | var ( 43 | rawData any 44 | err error 45 | parsedTag *tag.MesgTag 46 | ) 47 | 48 | switch { 49 | case head.TypeRef.Kind() == reflect.Uint8: 50 | rawData, err = reader.ReadUint8() 51 | 52 | case head.TypeRef.Kind() == reflect.Uint16: 53 | rawData, err = reader.ReadUint16() 54 | 55 | case head.TypeRef.Kind() == reflect.Uint32: 56 | rawData, err = reader.ReadUint32() 57 | 58 | case head.TypeRef.Kind() == reflect.Pointer: 59 | if head.ValueRef.IsNil() { 60 | head.ValueRef.Set(reflect.New(head.TypeRef.Elem())) 61 | } 62 | 63 | fields := make([]*RefField, 0, len(refFields)+1) 64 | fields = append(fields, &RefField{ 65 | StructName: head.StructName, 66 | FieldName: head.FieldName, 67 | Tag: head.Tag, 68 | TypeRef: head.TypeRef.Elem(), 69 | ValueRef: head.ValueRef.Elem(), 70 | }) 71 | fields = append(fields, refFields...) 72 | refFields = fields 73 | 74 | case head.TypeRef.Kind() == reflect.Slice && head.TypeRef.Elem().Kind() == reflect.Uint8: 75 | parsedTag, err = tag.NewMesgTag(head.Tag) 76 | 77 | if err != nil { 78 | return fmt.Errorf("cannot parse tag of field %s.%s", head.StructName, head.FieldName) 79 | } 80 | 81 | if parsedTag.Length == -1 { 82 | rawData, err = io.ReadAll(reader) 83 | } else if parsedTag.Length > 0 { 84 | rawData, err = reader.ReadFixedBytes(parsedTag.Length) 85 | } else { 86 | return fmt.Errorf("missing byte length for field %s.%s", head.StructName, head.FieldName) 87 | } 88 | 89 | case head.TypeRef.Kind() == reflect.String: 90 | parsedTag, err = tag.NewMesgTag(head.Tag) 91 | 92 | if err != nil { 93 | return fmt.Errorf("cannot parse tag of field %s.%s", head.StructName, head.FieldName) 94 | } 95 | 96 | if parsedTag.Length == -1 { 97 | rawData, err = reader.ReadRestAsString() 98 | } else if parsedTag.Length > 0 { 99 | rawData, err = reader.ReadFixedString(parsedTag.Length) 100 | } else { 101 | return fmt.Errorf("missing string length for field %s.%s", head.StructName, head.FieldName) 102 | } 103 | 104 | case head.TypeRef.Kind() == reflect.Struct: 105 | fields := refFields 106 | structFields := make([]*RefField, 0, head.TypeRef.NumField()) 107 | 108 | for i := 0; i < head.TypeRef.NumField(); i++ { 109 | structField := head.TypeRef.Field(i) 110 | fieldValueRef := head.ValueRef.Field(i) 111 | 112 | if structField.PkgPath != "" { 113 | continue 114 | } 115 | 116 | structFields = append(structFields, &RefField{ 117 | StructName: head.TypeRef.Name(), 118 | FieldName: structField.Name, 119 | Tag: structField.Tag.Get(tag.Name), 120 | TypeRef: structField.Type, 121 | ValueRef: fieldValueRef, 122 | }) 123 | } 124 | 125 | refFields = make([]*RefField, 0, len(fields)+len(structFields)) 126 | refFields = append(refFields, structFields...) 127 | refFields = append(refFields, fields...) 128 | 129 | default: 130 | return fmt.Errorf("unsupport field type %s", head.TypeRef) 131 | } 132 | 133 | if err != nil { 134 | return err 135 | } 136 | 137 | rawDataTypeRef := reflect.TypeOf(rawData) 138 | rawDataValueRef := reflect.ValueOf(rawData) 139 | 140 | if rawData != nil && rawDataTypeRef.Kind() != head.TypeRef.Kind() { 141 | return fmt.Errorf("can't set %s to field %s.%s, want %s", rawDataTypeRef.Kind(), head.StructName, head.FieldName, head.TypeRef.Kind()) 142 | } 143 | 144 | if rawData != nil { 145 | head.ValueRef.Set(rawDataValueRef) 146 | } 147 | } 148 | 149 | return nil 150 | } 151 | -------------------------------------------------------------------------------- /message/mesg_header.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/francistm/jt808-golang/internal/bytes" 7 | ) 8 | 9 | // PackHeader 消息体头部 10 | type PackHeader struct { 11 | // 消息 ID 12 | MessageID uint16 13 | 14 | // 消息体属性 15 | Property PackHeaderProperty 16 | 17 | // 协议版本号 (2019) 18 | VersionStep uint8 19 | 20 | // 终端手机号 21 | TerminalMobileNum string 22 | 23 | // 消息流水号 24 | SerialNum uint16 25 | 26 | // 消息包封装项 27 | Package *PackHeaderPackage 28 | } 29 | 30 | func (h *PackHeader) MarshalBinary() ([]byte, error) { 31 | var terminalMobileNumSize int 32 | 33 | if h.Property.Version == Version2013 { 34 | terminalMobileNumSize = 6 35 | } else if h.Property.Version == Version2019 { 36 | terminalMobileNumSize = 10 37 | } 38 | 39 | buf := bytes.NewBuffer() 40 | 41 | if err := buf.WriteUint16(h.MessageID); err != nil { 42 | return nil, err 43 | } 44 | 45 | b, err := h.Property.MarshalBinary() 46 | 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | if _, err = buf.Write(b); err != nil { 52 | return nil, err 53 | } 54 | 55 | if err := buf.WriteBCD(h.TerminalMobileNum, terminalMobileNumSize); err != nil { 56 | return nil, err 57 | } 58 | 59 | if err := buf.WriteUint16(h.SerialNum); err != nil { 60 | return nil, err 61 | } 62 | 63 | return buf.Bytes(), nil 64 | } 65 | 66 | func (h *PackHeader) UnmarshalBinary(in []byte) error { 67 | var ( 68 | err error 69 | reader = bytes.NewReader(in) 70 | 71 | mesgId uint16 72 | versionStep uint8 73 | terminalMobileNum string 74 | serialNum uint16 75 | packPackage *PackHeaderPackage 76 | ) 77 | 78 | mesgId, err = reader.ReadUint16() 79 | 80 | if err != nil { 81 | return err 82 | } 83 | 84 | propertyData, err := reader.ReadFixedBytes(2) 85 | 86 | if err != nil { 87 | return err 88 | } 89 | 90 | if err := h.Property.UnmarshalBinary(propertyData); err != nil { 91 | return err 92 | } 93 | 94 | if h.Property.Version == Version2013 { 95 | terminalMobileNum, err = reader.ReadBCD(6) 96 | 97 | if err != nil { 98 | return err 99 | } 100 | } else if h.Property.Version == Version2019 { 101 | versionStep, err = reader.ReadByte() 102 | 103 | if err != nil { 104 | return err 105 | } 106 | 107 | terminalMobileNum, err = reader.ReadBCD(10) 108 | 109 | if err != nil { 110 | return err 111 | } 112 | } 113 | 114 | serialNum, err = reader.ReadUint16() 115 | 116 | if err != nil { 117 | return err 118 | } 119 | 120 | if h.Property.IsMultiplePackage { 121 | pkgData, err := reader.ReadFixedBytes(4) 122 | 123 | if err != nil { 124 | return err 125 | } 126 | 127 | packPackage = new(PackHeaderPackage) 128 | 129 | if err := packPackage.UnmarshalBinary(pkgData); err != nil { 130 | return err 131 | } 132 | } 133 | 134 | h.MessageID = mesgId 135 | h.VersionStep = versionStep 136 | h.TerminalMobileNum = terminalMobileNum 137 | h.SerialNum = serialNum 138 | h.Package = packPackage 139 | 140 | return nil 141 | } 142 | 143 | type PackHeaderProperty struct { 144 | // 消息体长度 145 | BodyByteLength uint16 146 | 147 | // 数据加密方式 148 | IsEncrypted bool 149 | 150 | // 是否为长消息进行分包 151 | IsMultiplePackage bool 152 | 153 | // 版本表示 154 | Version uint8 155 | } 156 | 157 | func (p *PackHeaderProperty) MarshalBinary() ([]byte, error) { 158 | var ( 159 | i uint16 160 | out = make([]byte, 2) 161 | ) 162 | 163 | i |= p.BodyByteLength 164 | 165 | if p.IsEncrypted { 166 | i |= 0x01 << 10 167 | } 168 | 169 | if p.IsMultiplePackage { 170 | i |= 0x01 << 13 171 | } 172 | 173 | if p.Version == Version2019 { 174 | i |= 0x01 << 14 175 | } 176 | 177 | binary.BigEndian.PutUint16(out, i) 178 | 179 | return out, nil 180 | } 181 | 182 | func (p *PackHeaderProperty) UnmarshalBinary(data []byte) error { 183 | i := binary.BigEndian.Uint16(data) 184 | 185 | p.BodyByteLength = i & 0x03ff 186 | p.IsEncrypted = ((i >> 10) & 0x01) == 0x01 187 | p.IsMultiplePackage = ((i >> 13) & 0x01) == 0x01 188 | p.Version = uint8(i >> 14 & 0x01) 189 | 190 | return nil 191 | } 192 | 193 | type PackHeaderPackage struct { 194 | // 消息总包数 195 | Total uint16 196 | 197 | // 包序号,由 1 开始 198 | Index uint16 199 | } 200 | 201 | func (p *PackHeaderPackage) MarshalBinary() ([]byte, error) { 202 | b := bytes.NewBuffer() 203 | 204 | if err := b.WriteUint16(p.Total); err != nil { 205 | return nil, err 206 | } 207 | 208 | if err := b.WriteUint16(p.Index); err != nil { 209 | return nil, err 210 | } 211 | 212 | return b.Bytes(), nil 213 | } 214 | 215 | func (p *PackHeaderPackage) UnmarshalBinary(b []byte) error { 216 | r := bytes.NewReader(b) 217 | 218 | total, err := r.ReadUint16() 219 | 220 | if err != nil { 221 | return err 222 | } 223 | 224 | index, err := r.ReadUint16() 225 | 226 | if err != nil { 227 | return err 228 | } 229 | 230 | p.Index = index 231 | p.Total = total 232 | 233 | return nil 234 | } 235 | -------------------------------------------------------------------------------- /decoder_test.go: -------------------------------------------------------------------------------- 1 | package jt808 2 | 3 | import ( 4 | "image/jpeg" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/francistm/jt808-golang/internal/bytes" 10 | "github.com/francistm/jt808-golang/message" 11 | ) 12 | 13 | func Test_Unmarshal(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | mesgPack *message.MessagePack[message.MesgBody] 17 | data []byte 18 | assertFunc func(*testing.T, *message.MessagePack[message.MesgBody]) 19 | wantErr bool 20 | }{ 21 | { 22 | name: "mesg 0001", 23 | mesgPack: new(message.MessagePack[message.MesgBody]), 24 | data: []byte{0x7e, 0x00, 0x01, 0x00, 0x05, 0x01, 0x86, 0x57, 0x40, 0x59, 0x79, 0x00, 0x8f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x2f, 0x7e}, 25 | assertFunc: func(t *testing.T, mesgPack *message.MessagePack[message.MesgBody]) { 26 | assert.Equal(t, uint16(0x0001), mesgPack.PackHeader.MessageID) 27 | assert.Equal(t, uint16(0x0005), mesgPack.PackHeader.Property.BodyByteLength) 28 | assert.Equal(t, uint8(0x2f), mesgPack.Checksum) 29 | assert.True(t, mesgPack.ChecksumValid(), "checksum should valid") 30 | 31 | if assert.IsType(t, new(message.Body0001), mesgPack.PackBody) { 32 | packBody := mesgPack.PackBody.(*message.Body0001) 33 | 34 | assert.Equal(t, uint16(0x1011), packBody.AckMesgId) 35 | assert.Equal(t, uint16(0x1213), packBody.AckSerialId) 36 | assert.Equal(t, uint8(0x14), packBody.AckType) 37 | } 38 | }, 39 | }, 40 | { 41 | name: "mesg 0100 2013 version", 42 | mesgPack: new(message.MessagePack[message.MesgBody]), 43 | data: []byte{0x7E, 0x01, 0x00, 0x00, 0x36, 0x01, 0x72, 0x24, 0x60, 0x00, 0x94, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x36, 0x30, 0x30, 0x30, 0x39, 0x34, 0x00, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x99, 0x7E}, 44 | assertFunc: func(t *testing.T, mesgPack *message.MessagePack[message.MesgBody]) { 45 | assert.Equal(t, uint16(0x0100), mesgPack.PackHeader.MessageID) 46 | assert.Equal(t, uint16(0x0036), mesgPack.PackHeader.Property.BodyByteLength) 47 | assert.Equal(t, uint8(0x99), mesgPack.Checksum) 48 | 49 | if assert.IsType(t, new(message.Body0100_13), mesgPack.PackBody) { 50 | packBody := mesgPack.PackBody.(*message.Body0100_13) 51 | 52 | assert.Equal(t, "4600094", packBody.DeviceId) 53 | assert.Equal(t, "11111111111111111", packBody.LicencePlate) 54 | } 55 | }, 56 | wantErr: false, 57 | }, 58 | { 59 | name: "mesg 0200 - 1", 60 | mesgPack: new(message.MessagePack[message.MesgBody]), 61 | data: []byte{0x7e, 0x02, 0x00, 0x00, 0x26, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0xba, 0x7f, 0x0e, 0x07, 0xe4, 0xf1, 0x1c, 0x00, 0x28, 0x00, 0x3c, 0x00, 0x00, 0x18, 0x07, 0x15, 0x10, 0x10, 0x10, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x02, 0x02, 0x00, 0x37, 0x57, 0x7e}, 62 | assertFunc: func(t *testing.T, mesgPack *message.MessagePack[message.MesgBody]) { 63 | assert.Equal(t, uint16(0x0200), mesgPack.PackHeader.MessageID) 64 | assert.Equal(t, uint8(0x57), mesgPack.Checksum) 65 | assert.True(t, mesgPack.ChecksumValid(), "checksum should valid") 66 | 67 | if assert.IsType(t, new(message.Body0200), mesgPack.PackBody) { 68 | packBody := mesgPack.PackBody.(*message.Body0200) 69 | 70 | assert.Equal(t, uint32(0x01), packBody.WarnFlag) 71 | assert.Equal(t, uint32(0x02), packBody.StatusFlag) 72 | assert.Equal(t, 12.222222, packBody.Latitude()) 73 | assert.Equal(t, 132.444444, packBody.Longitude()) 74 | assert.Equal(t, float32(6), packBody.Speed()) 75 | 76 | extraMessage, err := packBody.ExtraMessage() 77 | 78 | if assert.NoError(t, err) { 79 | if assert.Contains(t, extraMessage, uint8(0x01)) { 80 | assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x64}, extraMessage[0x01]) 81 | } 82 | if assert.Contains(t, extraMessage, uint8(0x02)) { 83 | assert.Equal(t, []byte{0x00, 0x37}, extraMessage[0x02]) 84 | } 85 | } 86 | } 87 | }, 88 | }, 89 | { 90 | name: "mesg 0200 - 2", 91 | mesgPack: new(message.MessagePack[message.MesgBody]), 92 | data: []byte{0x7e, 0x02, 0x00, 0x00, 0x3c, 0x06, 0x48, 0x08, 0x35, 0x42, 0x96, 0x02, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x42, 0x02, 0x1f, 0xd9, 0x34, 0x07, 0x22, 0x75, 0x80, 0x00, 0x11, 0x02, 0x60, 0x01, 0x3a, 0x17, 0x08, 0x25, 0x14, 0x42, 0x57, 0x01, 0x04, 0x00, 0x04, 0x32, 0x92, 0x02, 0x02, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x25, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x11, 0x31, 0x01, 0x14, 0x77, 0x7d, 0x02, 0x1c, 0x00, 0x7e}, 93 | assertFunc: func(t *testing.T, mesgPack *message.MessagePack[message.MesgBody]) { 94 | assert.True(t, mesgPack.ChecksumValid()) 95 | assert.IsType(t, new(message.Body0200), mesgPack.PackBody) 96 | }, 97 | }, 98 | { 99 | name: "unknown mesg", 100 | mesgPack: new(message.MessagePack[message.MesgBody]), 101 | data: []byte{0x7e, 0x00, 0x00, 0x00, 0x05, 0x01, 0x86, 0x57, 0x40, 0x59, 0x79, 0x00, 0x8f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x2f, 0x7e}, 102 | wantErr: true, 103 | }, 104 | } 105 | 106 | for _, tt := range tests { 107 | t.Run(tt.name, func(t *testing.T) { 108 | err := Unmarshal(tt.data, tt.mesgPack) 109 | 110 | if tt.wantErr { 111 | t.Log(err) 112 | assert.Error(t, err) 113 | } else if assert.NoError(t, err) { 114 | tt.assertFunc(t, tt.mesgPack) 115 | } 116 | }) 117 | } 118 | } 119 | 120 | func Benchmark_Unmarshal0001(b *testing.B) { 121 | messagePack := message.MessagePack[*message.Body0001]{} 122 | buf := []byte{0x7e, 0x00, 0x01, 0x00, 0x05, 0x01, 0x86, 0x57, 0x40, 0x59, 0x79, 0x00, 0x8f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x99, 0x7e} 123 | 124 | for i := 0; i < b.N; i++ { 125 | if err := Unmarshal(buf, &messagePack); err != nil { 126 | b.Fatal(err) 127 | } 128 | } 129 | } 130 | 131 | func Test_ConcatUnmarshal(t *testing.T) { 132 | mesgDataList := [10][]byte{ 133 | {0x7e, 0x08, 0x01, 0x23, 0x24, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x31, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x27, 0x1d, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x20, 0x03, 0x01, 0xd2, 0x40, 0x0a, 0x06, 0x32, 0x96, 0xa5, 0x01, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x19, 0x01, 0x04, 0x09, 0x29, 0x52, 0xff, 0xd8, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, 0x07, 0x07, 0x09, 0x09, 0x08, 0x0a, 0x0c, 0x14, 0x0d, 0x0c, 0x0b, 0x0b, 0x0c, 0x19, 0x12, 0x13, 0x0f, 0x14, 0x1d, 0x1a, 0x1f, 0x1e, 0x1d, 0x1a, 0x1c, 0x1c, 0x20, 0x24, 0x2e, 0x27, 0x20, 0x22, 0x2c, 0x23, 0x1c, 0x1c, 0x28, 0x37, 0x29, 0x2c, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1f, 0x27, 0x39, 0x3d, 0x38, 0x32, 0x3c, 0x2e, 0x33, 0x34, 0x32, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x09, 0x09, 0x09, 0x0c, 0x0b, 0x0c, 0x18, 0x0d, 0x0d, 0x18, 0x32, 0x21, 0x1c, 0x21, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0xff, 0xfe, 0x00, 0x0b, 0x47, 0x50, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x0d, 0x09, 0x0a, 0x0b, 0x0a, 0x08, 0x0d, 0x0b, 0x0a, 0x0b, 0x0e, 0x0e, 0x0d, 0x0f, 0x13, 0x20, 0x15, 0x13, 0x12, 0x12, 0x13, 0x27, 0x1c, 0x1e, 0x17, 0x20, 0x2e, 0x29, 0x31, 0x30, 0x2e, 0x29, 0x2d, 0x2c, 0x33, 0x3a, 0x4a, 0x3e, 0x33, 0x36, 0x46, 0x37, 0x2c, 0x2d, 0x40, 0x57, 0x41, 0x46, 0x4c, 0x4e, 0x52, 0x53, 0x52, 0x32, 0x3e, 0x5a, 0x61, 0x5a, 0x50, 0x60, 0x4a, 0x51, 0x52, 0x4f, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x0e, 0x0e, 0x0e, 0x13, 0x11, 0x13, 0x26, 0x15, 0x15, 0x26, 0x4f, 0x35, 0x2d, 0x35, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0xf0, 0x01, 0x40, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x1c, 0x7e}, 134 | {0x7e, 0x08, 0x01, 0x23, 0x00, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x32, 0x00, 0x0a, 0x00, 0x02, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xdd, 0x00, 0x04, 0x00, 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x6c, 0x6a, 0x2a, 0x75, 0x5c, 0xe2, 0x99, 0xa5, 0xc9, 0x42, 0xe0, 0xf3, 0x52, 0x81, 0xc5, 0x00, 0x4a, 0xa7, 0x23, 0x14, 0xa5, 0x4e, 0x38, 0xa0, 0x7b, 0x88, 0x41, 0xef, 0x48, 0x40, 0xa0, 0x67, 0x3d, 0xe2, 0x1b, 0x4f, 0xf4, 0x98, 0xee, 0x40, 0x20, 0x38, 0xda, 0xde, 0x99, 0x1f, 0xe7, 0xf4, 0xac, 0xc1, 0x10, 0xf4, 0xa0, 0xcd, 0x8e, 0xf2, 0xf1, 0x40, 0x5c, 0xd0, 0x1d, 0x45, 0xf2, 0xe9, 0x36, 0x0a, 0x06, 0x2e, 0xdc, 0x57, 0x45, 0x61, 0x6a, 0x6d, 0xad, 0x51, 0x18, 0x61, 0xcf, 0xcc, 0xff, 0x00, 0x53, 0x49, 0x95, 0x12, 0xe0, 0x56, 0xcf, 0x14, 0x60, 0xe3, 0x34, 0x8a, 0x0e, 0xd4, 0xb8, 0xe3, 0x38, 0xfc, 0x28, 0x19, 0xcb, 0x5e, 0xdb, 0xfd, 0x9e, 0xe6, 0x48, 0xb1, 0x85, 0x56, 0xf9, 0x7f, 0xdd, 0x3d, 0x3f, 0x4a, 0xaf, 0xb4, 0xd3, 0x32, 0xea, 0x43, 0x3a, 0x65, 0x73, 0xe9, 0x55, 0x0d, 0x31, 0x31, 0xd1, 0x8c, 0x9c, 0x55, 0x80, 0x31, 0xc0, 0xa4, 0x08, 0x3a, 0x50, 0x30, 0x39, 0xa6, 0x0c, 0x42, 0xc2, 0x98, 0x4e, 0x4f, 0x4a, 0x06, 0x26, 0x0d, 0x37, 0x06, 0x90, 0x09, 0x8e, 0xf4, 0x75, 0x14, 0x00, 0xc6, 0x15, 0x13, 0x2d, 0x02, 0x18, 0x68, 0xa6, 0x21, 0x43, 0x1a, 0x33, 0x48, 0x0e, 0xf2, 0x35, 0xe0, 0x55, 0x95, 0x40, 0x3e, 0xb5, 0x4c, 0xbb, 0x0b, 0x8e, 0x70, 0x69, 0xdc, 0x0e, 0x3a, 0x9a, 0x41, 0xb1, 0x2a, 0x02, 0x4f, 0x4a, 0x9d, 0x40, 0xf4, 0xa1, 0x8c, 0x56, 0x51, 0xe9, 0x51, 0xc8, 0x81, 0x79, 0x26, 0x81, 0x94, 0xee, 0x61, 0x4b, 0x98, 0x9a, 0x27, 0x19, 0x46, 0x1f, 0xfe, 0xa3, 0x5c, 0xfd, 0xcd, 0xa4, 0xb6, 0xb2, 0xed, 0x71, 0xc1, 0xe5, 0x58, 0x74, 0x34, 0x5c, 0x96, 0xba, 0x91, 0x81, 0x9a, 0x70, 0x4c, 0x50, 0x21, 0x76, 0x13, 0xdb, 0x34, 0x9b, 0x39, 0x03, 0x1c, 0x9e, 0x94, 0x5c, 0x66, 0xa6, 0x9b, 0xa7, 0x94, 0x71, 0x3c, 0xeb, 0xf3, 0x0f, 0xb8, 0xbe, 0x9e, 0xe6, 0xb5, 0x40, 0xc1, 0xe9, 0x48, 0xa4, 0x87, 0x60, 0xe3, 0xa5, 0x26, 0xd2, 0xdc, 0x77, 0xa0, 0xa1, 0x44, 0x47, 0x1d, 0x29, 0x7c, 0xb6, 0x23, 0xa7, 0x14, 0x84, 0x64, 0x6b, 0xb6, 0x85, 0x23, 0x4b, 0x81, 0xd0, 0x1d, 0x8e, 0x7d, 0x01, 0x8f, 0x43, 0xf9, 0xff, 0x00, 0x3a, 0xc1, 0x63, 0x86, 0xc5, 0x52, 0x22, 0x5b, 0x83, 0x00, 0xc2, 0xa8, 0x4c, 0x8c, 0x8c, 0x4e, 0xd2, 0x47, 0xb5, 0x02, 0x61, 0x6c, 0xc0, 0x51, 0x7e}, 135 | {0x7e, 0x08, 0x01, 0x23, 0x00, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x33, 0x00, 0x0a, 0x00, 0x03, 0xb1, 0x04, 0x55, 0x8f, 0xe2, 0xc0, 0xa0, 0x56, 0x1a, 0x4a, 0x2f, 0xde, 0x60, 0x3e, 0xa6, 0x9a, 0x67, 0x88, 0x7f, 0x10, 0xfc, 0xa8, 0x0b, 0x8c, 0x33, 0xa9, 0x60, 0xaa, 0x0b, 0x13, 0xd0, 0x01, 0xd6, 0xa4, 0xf2, 0xe7, 0x3d, 0x2d, 0x2e, 0x3f, 0x18, 0x9a, 0x90, 0x6e, 0x42, 0x65, 0x3f, 0xdd, 0x23, 0xeb, 0x40, 0xde, 0xc3, 0x22, 0x9d, 0xc7, 0xb9, 0x1b, 0xc8, 0x55, 0xb1, 0x9e, 0x69, 0xc8, 0xdb, 0xc7, 0xd2, 0x90, 0x81, 0xba, 0xd3, 0x08, 0xa2, 0xe0, 0x30, 0x8a, 0x6e, 0x28, 0x10, 0x94, 0xb4, 0xc0, 0xef, 0xd4, 0x11, 0x52, 0xa8, 0x38, 0x15, 0x45, 0x96, 0x23, 0x51, 0x8c, 0x9a, 0x40, 0x00, 0x39, 0x1f, 0x85, 0x21, 0x92, 0xad, 0x4c, 0x89, 0x92, 0x33, 0x40, 0x13, 0x10, 0x31, 0xc5, 0x57, 0x93, 0x27, 0x82, 0x2a, 0x46, 0x45, 0xe5, 0xf5, 0x34, 0xbf, 0x66, 0x49, 0xc7, 0x94, 0xf1, 0x09, 0x03, 0x7f, 0x0e, 0x33, 0xf8, 0xfb, 0x53, 0x02, 0x26, 0xf0, 0xa1, 0x62, 0x1a, 0x39, 0xbc, 0xa1, 0xdc, 0x30, 0xdd, 0x4f, 0x8f, 0xc2, 0xd1, 0xa9, 0x1b, 0xe7, 0x91, 0xcf, 0xb1, 0x00, 0x7f, 0x2a, 0x9b, 0xb1, 0x59, 0x16, 0xa3, 0xf0, 0xf5, 0x9a, 0x0c, 0xb4, 0x79, 0x3e, 0xbb, 0x98, 0xff, 0x00, 0x5a, 0x63, 0x69, 0xf6, 0xd6, 0xf7, 0x0c, 0x62, 0x85, 0x54, 0x8c, 0x60, 0xe3, 0x27, 0xa5, 0x09, 0x0f, 0x4e, 0x82, 0xf9, 0x4b, 0x8e, 0x05, 0x02, 0x35, 0xeb, 0x8c, 0xd3, 0x19, 0x20, 0x00, 0xf6, 0xa7, 0x60, 0x63, 0xa0, 0xa6, 0x2b, 0x88, 0x40, 0xe8, 0x3a, 0xd3, 0x44, 0x7d, 0x02, 0xf4, 0x87, 0x72, 0x3b, 0x8b, 0x54, 0x9e, 0xdd, 0xe0, 0x90, 0x92, 0xb2, 0x29, 0x52, 0x7f, 0xad, 0x70, 0xd3, 0xc4, 0xe8, 0xed, 0x1b, 0x7d, 0x01, 0xe4, 0x62, 0xa7, 0xea, 0x0e, 0x0d, 0x34, 0x4b, 0x05, 0x53, 0x8a, 0x8a, 0x75, 0x20, 0x67, 0x6d, 0x04, 0x95, 0x61, 0x1f, 0xbd, 0x19, 0x07, 0xa5, 0x4e, 0xa3, 0xe6, 0x23, 0xde, 0x80, 0x33, 0x5d, 0x8b, 0x48, 0x49, 0xeb, 0x46, 0x33, 0x41, 0x24, 0xd6, 0x93, 0x7d, 0x01, 0x9a, 0xee, 0x19, 0xb3, 0x80, 0x8e, 0x0b, 0x7d, 0x01, 0x3a, 0x1f, 0xd0, 0x9a, 0xeb, 0xc0, 0x01, 0x87, 0x1d, 0x0e, 0x29, 0x33, 0x48, 0x1c, 0x83, 0xfd, 0xf6, 0xef, 0xc9, 0xfe, 0x75, 0x62, 0x0c, 0x6d, 0xe6, 0x93, 0xd8, 0x94, 0x52, 0xbb, 0x18, 0x97, 0x34, 0xd8, 0x25, 0x08, 0xff, 0x00, 0x37, 0x43, 0x4c, 0x4f, 0x72, 0xc8, 0x21, 0x86, 0x45, 0x46, 0xd4, 0xc0, 0x8c, 0x9a, 0x4e, 0xd4, 0x08, 0x4c, 0xd2, 0x52, 0x03, 0xd2, 0x84, 0x24, 0x0e, 0x68, 0xdb, 0x8e, 0x2a, 0xcb, 0x62, 0x8c, 0xf4, 0xa7, 0x28, 0x25, 0xb9, 0xa2, 0xc0, 0x58, 0x55, 0xe4, 0x7b, 0xd4, 0xc1, 0x69, 0x0c, 0x5e, 0xe3, 0x9f, 0xca, 0x8f, 0x2c, 0x13, 0x93, 0xd2, 0x90, 0x01, 0x51, 0xc8, 0xc0, 0xad, 0x2b, 0x18, 0x82, 0x40, 0x1f, 0x1f, 0x31, 0xef, 0xed, 0x43, 0xd8, 0x1e, 0xc5, 0x83, 0x49, 0x8f, 0x6a, 0x42, 0x43, 0x48, 0xac, 0xd9, 0xc6, 0x67, 0x73, 0xef, 0x8a, 0x10, 0xc6, 0x60, 0x0c, 0x53, 0x4a, 0xf3, 0xc5, 0x30, 0x14, 0x2f, 0x7c, 0x52, 0xed, 0xe2, 0x81, 0x8a, 0x07, 0x3c, 0xd2, 0x1a, 0x40, 0x34, 0xd7, 0x39, 0xe2, 0x1b, 0x41, 0x1d, 0xca, 0x5c, 0x81, 0x85, 0x98, 0x6d, 0x6f, 0x67, 0x1f, 0xe2, 0x3f, 0x95, 0x00, 0xf6, 0x31, 0x94, 0xe1, 0xb0, 0x69, 0xd2, 0x00, 0x57, 0xa5, 0x32, 0x4a, 0x9b, 0x3f, 0x7a, 0x18, 0x81, 0xc5, 0x3e, 0x45, 0x55, 0x60, 0x7d, 0x01, 0x68, 0x11, 0x97, 0x72, 0xbb, 0x6e, 0x5f, 0xd0, 0x9c, 0x8f, 0xc6, 0x9b, 0x41, 0x22, 0x75, 0xeb, 0x5d, 0x46, 0x8f, 0x78, 0xb7, 0x36, 0xb1, 0xc6, 0xcd, 0xfb, 0xd8, 0x80, 0x57, 0x1d, 0xc8, 0x1d, 0x1b, 0xfc, 0xf7, 0xa4, 0xca, 0x8b, 0xb3, 0x30, 0x58, 0x0d, 0xc4, 0x0f, 0x53, 0xfc, 0xea, 0x48, 0x86, 0x06, 0x29, 0x3d, 0x86, 0xad, 0x73, 0x3e, 0x6d, 0xdb, 0x8e, 0xf3, 0x92, 0x0d, 0x47, 0x4c, 0x82, 0xcd, 0xbb, 0x02, 0x0a, 0x9f, 0xc2, 0xa4, 0x65, 0xc8, 0xa0, 0x68, 0x81, 0x86, 0x29, 0x3b, 0x50, 0x21, 0x29, 0x28, 0x03, 0xd5, 0x94, 0x07, 0x4e, 0x7f, 0x0a, 0x89, 0xa1, 0x60, 0x72, 0x45, 0x52, 0x34, 0x60, 0x89, 0xba, 0xa5, 0x58, 0xc8, 0x61, 0xc6, 0x7f, 0x0a, 0x6c, 0x07, 0x77, 0xe9, 0x4a, 0xa0, 0xe7, 0x9a, 0x90, 0x1e, 0xbe, 0xf4, 0xfe, 0x31, 0x40, 0x0a, 0x3a, 0x67, 0x8a, 0xd4, 0xb7, 0x18, 0x82, 0x31, 0xfe, 0xcd, 0x0c, 0x4c, 0x7d, 0x01, 0x25, 0x21, 0x01, 0xac, 0xc9, 0x41, 0xf3, 0x1f, 0xfd, 0xe3, 0xfc, 0xe8, 0x40, 0x86, 0xe3, 0xbd, 0x2e, 0xd0, 0x06, 0x29, 0x94, 0x25, 0x2f, 0x14, 0x00, 0x9c, 0x62, 0x9b, 0x81, 0x48, 0x04, 0xe3, 0x8f, 0x5a, 0xaf, 0x7d, 0x01, 0x6a, 0xb7, 0x96, 0x72, 0xdb, 0x93, 0x82, 0xc3, 0x2a, 0x4f, 0x66, 0x1d, 0x0d, 0x03, 0x38, 0xa9, 0x10, 0x86, 0x39, 0x1b, 0x48, 0x24, 0x30, 0x3d, 0x88, 0xea, 0x29, 0xc8, 0x72, 0x28, 0x11, 0x13, 0xa6, 0x0f, 0x34, 0xc7, 0xe5, 0x73, 0xe9, 0x4c, 0x45, 0x2d, 0x41, 0x39, 0x47, 0xf6, 0xc5, 0x55, 0x03, 0x22, 0x82, 0x6c, 0x18, 0x14, 0xa3, 0x2a, 0xc1, 0xd5, 0x8a, 0xb0, 0xe8, 0x41, 0xc1, 0x1f, 0x8d, 0x02, 0x00, 0xcc, 0x3b, 0xd2, 0x89, 0xa4, 0x53, 0xf2, 0xb6, 0x3f, 0x00, 0x69, 0x31, 0xa6, 0x44, 0xe4, 0xb1, 0x24, 0x9e, 0x6a, 0x3a, 0x04, 0x3a, 0x27, 0xda, 0xe0, 0xd5, 0xcc, 0x82, 0x33, 0x40, 0xd0, 0xd2, 0xaa, 0x7a, 0xd3, 0x59, 0x06, 0x38, 0xa6, 0x32, 0xe7, 0x7e}, 136 | {0x7e, 0x08, 0x01, 0x23, 0x00, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x35, 0x00, 0x0a, 0x00, 0x04, 0x12, 0x08, 0xed, 0x49, 0x9f, 0x6a, 0x44, 0x9e, 0xad, 0x03, 0xf1, 0x52, 0x48, 0x46, 0x47, 0x4a, 0xbb, 0x1a, 0x02, 0x32, 0x82, 0x0e, 0x29, 0xdc, 0x1e, 0x7a, 0xd2, 0x01, 0x84, 0xe7, 0xa5, 0x2a, 0x93, 0x9a, 0x18, 0x0f, 0x3c, 0x1a, 0x07, 0x3d, 0xc5, 0x02, 0x14, 0x1c, 0x02, 0x06, 0x6b, 0x5e, 0x2f, 0xf5, 0x29, 0x8f, 0xee, 0x8a, 0x4c, 0x18, 0xb4, 0xb4, 0x84, 0x27, 0x7a, 0xcb, 0x39, 0x23, 0x24, 0xfb, 0xd3, 0x40, 0x26, 0xde, 0x39, 0x3d, 0x28, 0x0a, 0x31, 0xc0, 0xa0, 0xa1, 0x78, 0xf4, 0xa3, 0xad, 0x00, 0x1f, 0x85, 0x21, 0xa4, 0x02, 0x71, 0x48, 0x68, 0x03, 0x9b, 0xf1, 0x15, 0x9e, 0xcb, 0x85, 0xb9, 0x40, 0x76, 0x4d, 0xc3, 0x7b, 0x3f, 0xff, 0x00, 0x5c, 0x7f, 0x23, 0x58, 0x91, 0xb6, 0xd6, 0xc1, 0xa0, 0x43, 0xa5, 0x19, 0x19, 0x15, 0x12, 0x0c, 0x8c, 0x1a, 0x68, 0x45, 0x7b, 0xa4, 0x2d, 0x6a, 0xd9, 0xc1, 0x65, 0xe7, 0xfc, 0x6b, 0x38, 0x74, 0xa0, 0x4c, 0x3b, 0xd5, 0xab, 0x38, 0x37, 0x92, 0x3e, 0xce, 0x66, 0x66, 0x19, 0x51, 0xe6, 0x6d, 0xef, 0xfa, 0xfe, 0x74, 0x30, 0x44, 0x21, 0x14, 0x8c, 0xe0, 0x8c, 0xfb, 0xd3, 0x96, 0xdc, 0x36, 0x7d, 0x02, 0x6c, 0x52, 0xb8, 0x2d, 0x4a, 0xd2, 0xae, 0xc6, 0x2b, 0x9c, 0xe3, 0x8a, 0x84, 0xd0, 0x48, 0x0a, 0xbb, 0x6b, 0xf3, 0x47, 0xf8, 0xd0, 0x34, 0x4a, 0x54, 0x51, 0xb0, 0x1a, 0x65, 0x0d, 0x31, 0x83, 0x51, 0x98, 0x85, 0x02, 0x67, 0xa4, 0xc6, 0xc7, 0x8c, 0x54, 0xc3, 0x24, 0x64, 0xd5, 0x14, 0x03, 0xf3, 0xa7, 0x77, 0xce, 0x39, 0xfa, 0x50, 0x01, 0xc8, 0xe9, 0xd6, 0x8f, 0x28, 0xc9, 0x92, 0xcd, 0xc7, 0xa6, 0x29, 0x0c, 0x94, 0x28, 0x00, 0x50, 0x17, 0xb8, 0x14, 0x00, 0xe0, 0x01, 0x07, 0xe9, 0x5a, 0xd1, 0x9f, 0xdd, 0x27, 0xfb, 0xa2, 0x93, 0x13, 0x1b, 0x24, 0xb1, 0xc6, 0x32, 0xed, 0x8c, 0xfe, 0x75, 0x5d, 0xaf, 0x3f, 0xb8, 0x87, 0x1e, 0xad, 0xfe, 0x14, 0x84, 0x46, 0xd7, 0x33, 0x1f, 0xe2, 0x0b, 0xf4, 0x15, 0x0e, 0x6a, 0x87, 0x61, 0x79, 0xc5, 0x07, 0xeb, 0xcd, 0x20, 0x13, 0xbd, 0x2f, 0x7d, 0x02, 0xb4, 0x0c, 0x30, 0x3b, 0xd2, 0x60, 0x7a, 0xd2, 0x01, 0x30, 0x0f, 0x4a, 0x69, 0xc7, 0x7a, 0x01, 0x11, 0x5d, 0xdb, 0xa5, 0xd5, 0xbb, 0xc1, 0x27, 0xdd, 0x71, 0xd7, 0xfb, 0xa7, 0xb1, 0xfc, 0xeb, 0x89, 0xba, 0x82, 0x4b, 0x79, 0xde, 0x39, 0x54, 0x2b, 0xa1, 0xc3, 0x0f, 0x5f, 0x71, 0xed, 0x4d, 0x0d, 0x8d, 0x07, 0x72, 0x62, 0x91, 0x46, 0x1a, 0x82, 0x46, 0xb2, 0xfc, 0xec, 0xad, 0x92, 0xa4, 0x7d, 0x02, 0x9d, 0x0d, 0x63, 0x15, 0x2a, 0xe5, 0x5b, 0xa8, 0xe0, 0xd0, 0x26, 0x18, 0xab, 0xb6, 0xcc, 0xb0, 0x4f, 0x6f, 0x23, 0x4e, 0xa1, 0x32, 0xa5, 0x84, 0x6f, 0xc8, 0xee, 0x41, 0x14, 0x98, 0x96, 0x84, 0x39, 0xc2, 0x8f, 0xa5, 0x39, 0x32, 0x79, 0xed, 0x40, 0xd2, 0x2a, 0xdc, 0xae, 0x25, 0x3e, 0xf5, 0x5c, 0xd0, 0x89, 0x62, 0x55, 0xbb, 0x16, 0x1b, 0x8a, 0x9e, 0xb8, 0xc8, 0xa0, 0x16, 0xe5, 0xa7, 0x5a, 0x68, 0xe0, 0x50, 0x5a, 0xee, 0x14, 0x84, 0x53, 0x62, 0x3d, 0x06, 0x33, 0x91, 0x56, 0x57, 0x85, 0xe6, 0xad, 0xa1, 0x80, 0xfa, 0x71, 0x4e, 0xa4, 0x01, 0xd3, 0xa5, 0x38, 0x1c, 0xf7, 0xa0, 0x09, 0x09, 0xe3, 0xaf, 0x34, 0x29, 0xf9, 0x7d, 0x01, 0xa9, 0x00, 0xef, 0xa0, 0x35, 0xa6, 0x87, 0xf7, 0x69, 0xfe, 0xe8, 0xfe, 0x55, 0x2c, 0x19, 0x9e, 0xf9, 0x66, 0x66, 0x3f, 0x78, 0x93, 0xd6, 0x99, 0xc8, 0x15, 0x40, 0x21, 0x3f, 0x4a, 0x33, 0x40, 0x0b, 0xc9, 0x1e, 0xd4, 0x1e, 0xbd, 0x28, 0x18, 0x1e, 0x7b, 0xd0, 0x29, 0x00, 0xbe, 0xb4, 0x9c, 0x03, 0x9a, 0x00, 0x4e, 0x86, 0x8c, 0x7a, 0xd2, 0x18, 0xd3, 0xd3, 0x9a, 0xc9, 0xd7, 0x74, 0xff, 0x00, 0xb4, 0x42, 0x2e, 0x62, 0x5c, 0xcb, 0x18, 0xc3, 0x00, 0x3e, 0xfa, 0xff, 0x00, 0x88, 0xff, 0x00, 0x1f, 0x6a, 0x01, 0x9c, 0xc0, 0x18, 0xe5, 0x4e, 0x41, 0xa5, 0x3c, 0x72, 0x69, 0x92, 0x24, 0xbf, 0xc2, 0xc2, 0xb2, 0xaf, 0x97, 0x6c, 0xf9, 0xec, 0xdc, 0xd0, 0x26, 0x41, 0x9a, 0xb5, 0x76, 0xe8, 0x60, 0xb4, 0xdb, 0x22, 0xbb, 0x2c, 0x65, 0x5b, 0x6f, 0x6e, 0x84, 0x03, 0xef, 0x83, 0x49, 0x88, 0x80, 0x1c, 0xf3, 0x4f, 0xdc, 0x7a, 0x0a, 0x01, 0x15, 0xe6, 0x2c, 0x4f, 0xcc, 0x72, 0x45, 0x40, 0x7a, 0xd0, 0x26, 0x25, 0x3a, 0x36, 0x2a, 0xc1, 0x87, 0x51, 0xc8, 0xa0, 0x48, 0xd5, 0x46, 0x12, 0x44, 0x18, 0x74, 0x22, 0x98, 0x46, 0x0f, 0x34, 0xcd, 0x3a, 0x07, 0x7a, 0x42, 0x28, 0xb0, 0x8f, 0x40, 0x4e, 0xd8, 0xa9, 0xd7, 0x3d, 0xeb, 0x41, 0x92, 0x05, 0x07, 0xa5, 0x2e, 0x30, 0x29, 0x02, 0x13, 0xb5, 0x0b, 0xd7, 0x93, 0x48, 0x09, 0x39, 0xa7, 0xa8, 0x07, 0xf1, 0xa4, 0x03, 0xfa, 0x0e, 0xb5, 0xa3, 0x1f, 0x31, 0xa7, 0xa6, 0xd1, 0xfc, 0xa9, 0x30, 0x65, 0x19, 0x07, 0xcc, 0xdf, 0xef, 0x1f, 0xe7, 0x4d, 0xc6, 0x47, 0x34, 0x01, 0x19, 0xeb, 0xc1, 0x14, 0x77, 0xa6, 0x02, 0xf3, 0x4b, 0xc6, 0x28, 0x00, 0x3d, 0x29, 0x05, 0x20, 0x16, 0x90, 0x9a, 0x00, 0x43, 0xcf, 0xad, 0x21, 0xe2, 0x81, 0xa0, 0x03, 0xe5, 0xe6, 0x91, 0xb8, 0x3c, 0x1a, 0x45, 0x1c, 0xe6, 0xb3, 0xa6, 0x88, 0x19, 0xae, 0xa1, 0x5f, 0xdc, 0xb1, 0xf9, 0x94, 0x7f, 0x01, 0x3d, 0xfe, 0x9f, 0xca, 0xb1, 0x67, 0x46, 0xd9, 0xf2, 0x11, 0x9a, 0x09, 0x63, 0x63, 0x05, 0xf1, 0x7e}, 137 | {0x7e, 0x08, 0x01, 0x23, 0x00, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x36, 0x00, 0x0a, 0x00, 0x05, 0xe0, 0xd8, 0x73, 0x95, 0xe3, 0x9a, 0xa7, 0x7d, 0x02, 0xbb, 0xe0, 0x49, 0x07, 0x6f, 0xeb, 0x4c, 0x92, 0x81, 0x27, 0x61, 0x23, 0xae, 0x2b, 0x57, 0x56, 0xb6, 0x92, 0x23, 0x12, 0x0c, 0x18, 0x53, 0x70, 0x8f, 0x1d, 0xb2, 0x72, 0x7f, 0x1e, 0x83, 0xf0, 0xa4, 0xdd, 0x84, 0x8c, 0xec, 0x1c, 0xfd, 0x29, 0xca, 0xac, 0x4f, 0x14, 0x01, 0x1d, 0xcc, 0x4e, 0xa4, 0xb3, 0x10, 0x41, 0xf4, 0xaa, 0xad, 0xd6, 0x81, 0x35, 0x61, 0x28, 0x14, 0x09, 0x17, 0xac, 0x64, 0xf9, 0x8c, 0x4d, 0xdf, 0x95, 0xfe, 0xb5, 0x3b, 0xaf, 0xcd, 0x4e, 0xe5, 0xa0, 0xe9, 0x45, 0x21, 0x9e, 0x80, 0x82, 0xa5, 0x5a, 0xd0, 0x09, 0x94, 0x8c, 0x0a, 0x0f, 0x5e, 0x69, 0x00, 0x98, 0xe6, 0x94, 0x60, 0x1e, 0x68, 0x04, 0x38, 0x75, 0x22, 0x9c, 0x0f, 0x63, 0x48, 0x07, 0x82, 0x08, 0xc5, 0x58, 0x13, 0xb9, 0x55, 0x8e, 0x24, 0xe4, 0x00, 0x29, 0x01, 0x0b, 0xee, 0x04, 0x82, 0x39, 0xcf, 0x34, 0xc2, 0x4f, 0xe1, 0x40, 0x08, 0x79, 0xe8, 0x69, 0x33, 0xc5, 0x30, 0x17, 0x3c, 0x71, 0x45, 0x00, 0x28, 0xe6, 0x8e, 0x7b, 0x52, 0x00, 0xcf, 0x1c, 0xd1, 0xd2, 0x81, 0x89, 0xcf, 0x7a, 0x6f, 0x7a, 0x00, 0x33, 0xe9, 0x41, 0xa4, 0x31, 0xac, 0x01, 0x04, 0x10, 0x08, 0x23, 0x04, 0x1e, 0xf5, 0xce, 0x6a, 0x7a, 0x5b, 0x5b, 0xee, 0x96, 0x00, 0x5a, 0xdf, 0xa9, 0x5e, 0xf1, 0xff, 0x00, 0x88, 0xa0, 0x0c, 0x64, 0x4f, 0x2d, 0xfe, 0x5f, 0xba, 0x6a, 0x39, 0x63, 0xdc, 0x92, 0x46, 0x3b, 0x82, 0x47, 0xf9, 0xfa, 0xd3, 0x25, 0x99, 0x31, 0xa6, 0xf9, 0x16, 0x3e, 0x9b, 0xc8, 0x5f, 0xcf, 0x8a, 0xeb, 0x6f, 0x91, 0x5e, 0xca, 0xe5, 0x99, 0x41, 0x02, 0x37, 0x23, 0x23, 0xb8, 0x07, 0x06, 0xa5, 0x84, 0x7a, 0x9c, 0xb3, 0x70, 0xdc, 0x53, 0x90, 0xf4, 0xe6, 0x9b, 0x12, 0xdc, 0x5b, 0xbe, 0x62, 0xac, 0xe6, 0xa5, 0x10, 0x9e, 0xe3, 0x49, 0xa0, 0x1a, 0x64, 0x12, 0x2b, 0x30, 0x21, 0x94, 0xe0, 0x83, 0x91, 0x5a, 0x88, 0xe2, 0x54, 0x57, 0x1d, 0x08, 0xfc, 0xa8, 0x45, 0x21, 0x31, 0xcd, 0x25, 0x31, 0x9e, 0x82, 0xbd, 0x29, 0xea, 0x73, 0x5a, 0x0c, 0x91, 0x78, 0x1c, 0x71, 0x4b, 0x48, 0x07, 0x72, 0x0d, 0x1d, 0x4d, 0x20, 0x14, 0x1f, 0xf3, 0x8a, 0x7f, 0xe1, 0x49, 0x80, 0xe1, 0x9c, 0x70, 0x2b, 0x46, 0x05, 0x55, 0x85, 0x48, 0xee, 0x32, 0x6a, 0x58, 0x15, 0x6e, 0x06, 0x26, 0x73, 0xea, 0x47, 0xf2, 0x15, 0x09, 0x07, 0x1c, 0x53, 0x43, 0x1a, 0x69, 0xb4, 0xc4, 0x2f, 0x23, 0x8a, 0x51, 0x40, 0x07, 0x1d, 0x28, 0xa4, 0x02, 0xfd, 0x68, 0xeb, 0xcd, 0x03, 0x10, 0xb7, 0x18, 0xa4, 0x14, 0x00, 0x1e, 0x3a, 0xd4, 0x4c, 0xf8, 0xe0, 0x0c, 0xd2, 0x1d, 0x80, 0x33, 0x15, 0x19, 0x1c, 0xd0, 0x09, 0xc6, 0x68, 0x19, 0xca, 0xf8, 0x99, 0x6d, 0xec, 0x66, 0x80, 0xc3, 0x19, 0x59, 0x26, 0xdc, 0x59, 0x41, 0x1b, 0x70, 0x3b, 0x8f, 0x7c, 0x9e, 0x9d, 0x2b, 0x14, 0x5d, 0x6e, 0xb8, 0x0a, 0x57, 0x82, 0x70, 0x0e, 0x7f, 0x2a, 0x08, 0x6e, 0xcc, 0x86, 0xe5, 0x4d, 0xb5, 0xd4, 0x77, 0x0a, 0xbb, 0x94, 0x38, 0x70, 0x3d, 0x48, 0x39, 0x22, 0xb7, 0xde, 0xee, 0x0b, 0xad, 0x32, 0x59, 0x60, 0x7d, 0x01, 0xca, 0x40, 0x05, 0x7b, 0xae, 0x48, 0x18, 0x23, 0xf1, 0xa1, 0x82, 0xdc, 0xe6, 0xdc, 0x0d, 0xec, 0x3d, 0x09, 0xa6, 0x83, 0x86, 0xcd, 0x02, 0x24, 0xb8, 0x78, 0xcc, 0x24, 0x09, 0x10, 0x92, 0x3b, 0x10, 0x6b, 0x3d, 0xe9, 0x20, 0x93, 0x19, 0x49, 0x4c, 0x81, 0xc2, 0xae, 0x58, 0xcb, 0xb4, 0xb4, 0x67, 0xa1, 0xe4, 0x7d, 0x01, 0x7b, 0xd0, 0x86, 0x8b, 0x84, 0x53, 0x28, 0x2c, 0xf4, 0x25, 0xc6, 0x06, 0x69, 0xc0, 0x81, 0x5a, 0x88, 0x70, 0x3e, 0x94, 0xe1, 0xf5, 0xa4, 0x31, 0x7a, 0x91, 0x4b, 0x9a, 0x00, 0x50, 0x79, 0xa7, 0x29, 0xf7, 0xa4, 0x04, 0xd1, 0xf4, 0xe6, 0xb4, 0x21, 0x23, 0xc9, 0x5e, 0x73, 0xc5, 0x43, 0x06, 0x55, 0xba, 0xc7, 0x9b, 0xf8, 0x54, 0x54, 0xc6, 0x84, 0x23, 0x35, 0x19, 0x18, 0x38, 0xa1, 0x00, 0x64, 0x66, 0x8c, 0xe7, 0xb5, 0x31, 0x05, 0x2e, 0x68, 0x18, 0x12, 0x33, 0x46, 0x71, 0x48, 0x04, 0xc8, 0xcf, 0x14, 0x84, 0x8c, 0xd1, 0x60, 0x18, 0xfc, 0xf5, 0xa3, 0xe5, 0xce, 0x33, 0xcd, 0x22, 0x83, 0x34, 0x9c, 0xd2, 0x02, 0xb5, 0xe5, 0xa5, 0xad, 0xe2, 0xaa, 0x5d, 0xdb, 0xa4, 0xa1, 0x4e, 0x54, 0x38, 0xe9, 0x5c, 0x87, 0x88, 0xec, 0xa1, 0xb3, 0xd5, 0x63, 0x68, 0x22, 0x10, 0xc3, 0x2c, 0x61, 0xf0, 0xa3, 0x0b, 0x90, 0x48, 0x38, 0x1d, 0xb8, 0x03, 0xf3, 0xa0, 0x52, 0xd8, 0x85, 0x95, 0x59, 0x0c, 0x72, 0x8f, 0x62, 0x0f, 0x51, 0x54, 0xe4, 0xb2, 0x2a, 0xfb, 0xa0, 0x7c, 0xfe, 0x38, 0x3f, 0x9d, 0x32, 0x1a, 0x20, 0x65, 0x9d, 0x3e, 0xf2, 0x31, 0xf7, 0xc6, 0x6a, 0x32, 0xf9, 0x23, 0x22, 0x81, 0x0e, 0x77, 0x84, 0xa7, 0x11, 0xb0, 0x6c, 0x7d, 0x02, 0x1f, 0xce, 0xaa, 0xb7, 0x4a, 0x10, 0x98, 0xca, 0x4a, 0x04, 0x39, 0x6a, 0x48, 0xd5, 0xf7, 0x0d, 0x80, 0xee, 0x07, 0x8a, 0x01, 0x1a, 0xad, 0x8e, 0x71, 0x51, 0xe6, 0x83, 0x44, 0x7a, 0x10, 0x3c, 0x53, 0x87, 0xbd, 0x6a, 0x21, 0x47, 0x14, 0xe0, 0x7b, 0x52, 0x18, 0xee, 0xd4, 0x9c, 0xd2, 0x10, 0xb5, 0x22, 0x8c, 0xd0, 0xc6, 0x89, 0x15, 0x70, 0x2a, 0xfd, 0xb7, 0x30, 0x2f, 0xe3, 0xfc, 0xea, 0x58, 0xd8, 0xc9, 0xe2, 0xdf, 0x86, 0x51, 0xcd, 0x46, 0xb1, 0x7e}, 138 | {0x7e, 0x08, 0x01, 0x23, 0x00, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x37, 0x00, 0x0a, 0x00, 0x06, 0x2d, 0x98, 0xfd, 0xe2, 0x00, 0xfc, 0xe9, 0x5c, 0x02, 0x68, 0x91, 0x21, 0xca, 0xe4, 0xb6, 0x47, 0x24, 0xd5, 0x53, 0xd7, 0x34, 0xd0, 0x09, 0x8a, 0x29, 0x88, 0x07, 0x5e, 0x29, 0x73, 0x9a, 0x06, 0x1c, 0x75, 0xa4, 0x3d, 0x69, 0x00, 0xa0, 0x0f, 0x7c, 0xd2, 0x1a, 0x01, 0x0d, 0x23, 0x35, 0x1f, 0x92, 0xa4, 0xe4, 0x9a, 0x43, 0x24, 0x03, 0x1c, 0x0a, 0x42, 0x30, 0x0f, 0xaf, 0xa5, 0x03, 0x21, 0x66, 0x97, 0x23, 0x6c, 0x68, 0x47, 0xbb, 0x91, 0xfd, 0x2b, 0x03, 0xc6, 0x36, 0xcd, 0x2e, 0x9b, 0x0c, 0xdb, 0x48, 0x31, 0x49, 0xb4, 0x90, 0x73, 0x85, 0x61, 0x83, 0xfa, 0x85, 0xa2, 0xe2, 0x6b, 0x42, 0xe2, 0xed, 0xbd, 0xd3, 0xad, 0xe5, 0xb8, 0xb4, 0x33, 0xbc, 0xb0, 0x23, 0x33, 0x2e, 0xd0, 0x72, 0x54, 0x13, 0x82, 0x58, 0x11, 0x59, 0x5a, 0xa6, 0x9d, 0x0c, 0x16, 0xed, 0x73, 0x0a, 0x5c, 0xc2, 0xc1, 0x95, 0x42, 0x33, 0x23, 0x2f, 0x24, 0x0f, 0x52, 0x7f, 0x5a, 0x41, 0x6b, 0x98, 0x4d, 0x75, 0x87, 0x2a, 0xca, 0xc3, 0x07, 0x06, 0x9a, 0x6e, 0xf9, 0xfb, 0xb9, 0x14, 0xc8, 0x12, 0x49, 0x61, 0x29, 0xcc, 0x0c, 0x0b, 0x74, 0x2c, 0x80, 0x67, 0xf1, 0xaa, 0x0d, 0x40, 0x98, 0xca, 0x4a, 0x09, 0x1d, 0x19, 0xc3, 0x83, 0xef, 0x5a, 0x88, 0x46, 0x06, 0x05, 0x05, 0x44, 0x52, 0x49, 0xed, 0x4d, 0x39, 0xc5, 0x05, 0x1e, 0x86, 0x01, 0xed, 0x4a, 0x2b, 0x51, 0x74, 0x17, 0x9a, 0x72, 0xd2, 0x01, 0xe7, 0x91, 0xd6, 0x80, 0x29, 0x05, 0x83, 0xda, 0xa4, 0x4f, 0x73, 0x40, 0xd1, 0x30, 0xe9, 0x56, 0xed, 0x8f, 0xee, 0xb1, 0xe8, 0x6a, 0x18, 0xc9, 0x68, 0xa4, 0x04, 0x17, 0x47, 0x11, 0x71, 0xd7, 0x35, 0x48, 0x90, 0x3a, 0xd5, 0x20, 0x13, 0x27, 0xa5, 0x1d, 0x45, 0x30, 0x14, 0x74, 0xa4, 0x3d, 0x78, 0xa0, 0x41, 0xd2, 0x83, 0xf5, 0xa0, 0x62, 0x13, 0x4b, 0xee, 0x68, 0x00, 0xed, 0x4d, 0xcd, 0x21, 0xa1, 0x33, 0xc5, 0x1d, 0x45, 0x21, 0xdc, 0x43, 0xef, 0x54, 0x75, 0x8b, 0x63, 0x75, 0xa4, 0x5d, 0xc0, 0x01, 0x66, 0x68, 0x89, 0x50, 0x3a, 0x96, 0x1c, 0xaf, 0xea, 0x05, 0x20, 0x65, 0x0f, 0x0c, 0xca, 0xb2, 0xe8, 0x16, 0xe1, 0x58, 0xb1, 0x42, 0xe8, 0x73, 0xdb, 0xe6, 0x24, 0x0f, 0xc8, 0x8a, 0x67, 0x89, 0x24, 0xf2, 0xec, 0x22, 0x00, 0xf0, 0xf3, 0xa8, 0x39, 0xf4, 0xda, 0xc7, 0xf9, 0x81, 0x40, 0x93, 0xd0, 0xe3, 0x65, 0x21, 0xa6, 0x76, 0x1c, 0xe5, 0x89, 0x1f, 0x9d, 0x30, 0x7d, 0x01, 0xea, 0x64, 0x2d, 0xcd, 0x28, 0x11, 0x9e, 0x10, 0x3c, 0xc7, 0x00, 0xf1, 0x81, 0x8a, 0xc8, 0x71, 0xc9, 0xa8, 0x88, 0xe6, 0xb4, 0x44, 0x54, 0x55, 0x99, 0x80, 0xab, 0xf6, 0xd3, 0x06, 0xf9, 0x48, 0xe7, 0x19, 0xa0, 0xa4, 0x4a, 0xe4, 0x8a, 0x8c, 0x92, 0x7b, 0xd0, 0xca, 0x3d, 0x21, 0x47, 0x4a, 0x76, 0xda, 0xd4, 0x05, 0xed, 0x4a, 0x05, 0x21, 0x0a, 0x4d, 0x00, 0xfb, 0xd0, 0x02, 0x8f, 0xca, 0xa4, 0x4c, 0xd2, 0x63, 0x24, 0xc9, 0xc6, 0x0d, 0x5a, 0xb5, 0x27, 0x61, 0x07, 0xd6, 0xa5, 0x8c, 0x9b, 0xa5, 0x21, 0x38, 0x07, 0xda, 0xa4, 0x0a, 0x72, 0xc8, 0x5d, 0xb3, 0xdb, 0xb6, 0x6a, 0x02, 0x39, 0x3d, 0x71, 0x56, 0x80, 0x3b, 0x51, 0xda, 0x80, 0x14, 0x1a, 0x0d, 0x00, 0x37, 0x9c, 0xd1, 0x9e, 0x69, 0x80, 0x73, 0x4a, 0x6a, 0x40, 0x4c, 0xfe, 0x22, 0x93, 0xad, 0x03, 0x0c, 0xf3, 0xd6, 0x92, 0x90, 0x08, 0x7a, 0xd2, 0x67, 0x04, 0x1f, 0x43, 0x9a, 0x06, 0x73, 0xde, 0x1b, 0x51, 0x6a, 0x75, 0x2d, 0x3c, 0x2b, 0x0f, 0xb3, 0xdc, 0x92, 0xa4, 0xf7, 0x52, 0x30, 0x3f, 0x44, 0xcf, 0xe3, 0x51, 0xf8, 0xad, 0x87, 0xd8, 0x20, 0x1d, 0x49, 0x9f, 0x3f, 0x92, 0x37, 0xf8, 0x8a, 0x5d, 0x45, 0x7f, 0x74, 0xe4, 0x89, 0xe4, 0xd2, 0x0e, 0xb4, 0xcc, 0xcd, 0x4b, 0x63, 0xfb, 0xa0, 0x73, 0x8a, 0xc8, 0x98, 0x61, 0xd8, 0x7b, 0xd4, 0xc7, 0x72, 0xe7, 0xb1, 0x01, 0xa4, 0x35, 0x46, 0x40, 0x2a, 0xcd, 0xb1, 0xfd, 0xe7, 0xe1, 0x40, 0xe2, 0x59, 0x66, 0xcd, 0x30, 0x9a, 0x45, 0x1e, 0x96, 0xb8, 0xc5, 0x3b, 0x39, 0x1c, 0x56, 0xcc, 0x03, 0x23, 0x34, 0xb9, 0xed, 0x4b, 0xa0, 0x07, 0x22, 0x94, 0x73, 0x40, 0x0e, 0x5f, 0x5e, 0xf5, 0x20, 0x34, 0x98, 0xc7, 0x83, 0xed, 0x56, 0x6d, 0x8f, 0x2c, 0x3f, 0x1a, 0x96, 0x32, 0x7c, 0x8f, 0x4a, 0xad, 0x71, 0x26, 0xef, 0x91, 0x7a, 0x0e, 0xb4, 0x90, 0x10, 0x53, 0x48, 0xf6, 0xa6, 0x03, 0x73, 0xda, 0x81, 0xf4, 0xaa, 0x00, 0xe3, 0x34, 0x66, 0x90, 0x83, 0xa7, 0x7d, 0x02, 0xb4, 0x99, 0x39, 0xa0, 0x00, 0x75, 0xa3, 0x18, 0x34, 0x0c, 0x3f, 0x4a, 0x0e, 0x29, 0x0c, 0x4e, 0xf4, 0x87, 0xaf, 0x14, 0x86, 0x23, 0x53, 0x37, 0x60, 0x8c, 0x73, 0xcd, 0x0c, 0x67, 0x3d, 0xa0, 0xe9, 0xc6, 0x3b, 0x89, 0x6f, 0x9a, 0xef, 0xce, 0x62, 0x64, 0x81, 0x81, 0x4e, 0x49, 0x0e, 0x06, 0x49, 0xcf, 0xa2, 0x83, 0x8c, 0x77, 0xaa, 0xfe, 0x2e, 0x3f, 0x2d, 0x90, 0xc9, 0xc7, 0xef, 0x4f, 0xfe, 0x81, 0xfe, 0x34, 0x91, 0x3f, 0x64, 0xe5, 0x8f, 0x5a, 0x55, 0x04, 0x9e, 0x29, 0x99, 0x9a, 0xb0, 0x21, 0xf2, 0xc2, 0xe3, 0x9a, 0xc8, 0xb9, 0xc7, 0x9c, 0xf8, 0xe0, 0x66, 0xa2, 0x2f, 0x52, 0xe7, 0xb1, 0x5c, 0xd2, 0x55, 0x99, 0x00, 0xa9, 0xad, 0xff, 0x00, 0xd6, 0x0a, 0x06, 0xb7, 0x2e, 0x37, 0x4a, 0x61, 0xa4, 0x59, 0xe9, 0x78, 0x18, 0xa3, 0x3c, 0x73, 0x5b, 0x51, 0x7e}, 139 | {0x7e, 0x08, 0x01, 0x23, 0x00, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x39, 0x00, 0x0a, 0x00, 0x07, 0x08, 0x37, 0x53, 0x86, 0x0d, 0x02, 0x01, 0xeb, 0x47, 0x42, 0x49, 0xa4, 0x31, 0xc0, 0xe3, 0xbe, 0x69, 0xe0, 0xfe, 0xb4, 0x0c, 0x78, 0xe9, 0xcd, 0x4f, 0x6e, 0xe1, 0x64, 0xe4, 0xf0, 0xc3, 0x15, 0x2c, 0x09, 0xe5, 0x7f, 0x2d, 0x38, 0xfb, 0xc7, 0xa5, 0x53, 0xe4, 0x9e, 0x7a, 0x9a, 0x48, 0x62, 0x8c, 0xd2, 0x05, 0xc0, 0xed, 0x40, 0x0d, 0xf6, 0xa4, 0xeb, 0xc8, 0x35, 0x42, 0x0a, 0x3a, 0x74, 0xa0, 0x04, 0x34, 0x63, 0xb5, 0x20, 0x10, 0x67, 0xd4, 0x53, 0xba, 0xd0, 0x31, 0x0d, 0x25, 0x21, 0x85, 0x26, 0x46, 0x29, 0x0c, 0x61, 0x39, 0xa6, 0xe3, 0x07, 0xe9, 0x40, 0x18, 0xfa, 0x08, 0x0a, 0x9a, 0x8a, 0xfa, 0x6a, 0x33, 0x0f, 0xfd, 0x06, 0xb2, 0x7c, 0x5b, 0x21, 0x37, 0x76, 0xf1, 0xe7, 0x85, 0x88, 0x9c, 0x7d, 0x01, 0x5b, 0xff, 0x00, 0xb1, 0xfd, 0x28, 0x44, 0xbd, 0x8e, 0x74, 0xd2, 0xae, 0x14, 0x8d, 0xd4, 0x32, 0x11, 0x78, 0x5f, 0x43, 0x18, 0xea, 0xcc, 0x7f, 0xd9, 0x5f, 0xf1, 0xac, 0xe9, 0xdc, 0x49, 0x2b, 0x38, 0x18, 0xc9, 0x27, 0x06, 0xa5, 0x22, 0xe7, 0x2b, 0xab, 0x10, 0x1a, 0x61, 0xaa, 0x32, 0x01, 0x52, 0xc2, 0x70, 0xe2, 0x80, 0x45, 0xee, 0xa2, 0x9b, 0xde, 0x91, 0xa1, 0xe9, 0x00, 0xe4, 0x0c, 0xe2, 0x9d, 0xf4, 0xad, 0x84, 0x18, 0x1f, 0xe3, 0x4b, 0xdb, 0x14, 0x80, 0x17, 0xa7, 0xad, 0x29, 0x19, 0x1c, 0x1e, 0x28, 0x00, 0x45, 0x23, 0xbf, 0xeb, 0x52, 0xaf, 0x4a, 0x06, 0x87, 0x67, 0x8a, 0x5c, 0xf0, 0x3f, 0xc6, 0x90, 0x0f, 0xdc, 0x4f, 0x2c, 0x72, 0x69, 0x3d, 0x71, 0x9a, 0x40, 0x81, 0x4f, 0x34, 0xee, 0x31, 0x91, 0x48, 0x77, 0x18, 0xc7, 0x8f, 0x53, 0x4c, 0xfa, 0x55, 0x21, 0x05, 0x27, 0xd2, 0x90, 0x05, 0x28, 0xa6, 0x30, 0x1d, 0x68, 0xed, 0x48, 0x03, 0xeb, 0x49, 0xf5, 0xa4, 0x34, 0x27, 0xe3, 0x48, 0x79, 0xa4, 0x30, 0xf6, 0xa6, 0xb7, 0x1d, 0xe9, 0x01, 0x85, 0xa5, 0x4d, 0x1c, 0x17, 0xfa, 0x9d, 0x9c, 0xce, 0x89, 0x3b, 0xde, 0x3c, 0xc8, 0xac, 0xc0, 0x17, 0x57, 0xc1, 0x18, 0xf5, 0xe9, 0x54, 0xf5, 0x8b, 0x34, 0xbe, 0xf1, 0x0c, 0x56, 0xf2, 0xdc, 0xa5, 0xb4, 0x62, 0xd4, 0x34, 0x92, 0x30, 0xe8, 0x03, 0xbf, 0x41, 0xdc, 0x92, 0x40, 0xfc, 0x68, 0xb9, 0x3b, 0xa3, 0x3a, 0xef, 0x4d, 0xb4, 0x79, 0xc4, 0x7a, 0x79, 0xb9, 0x28, 0xbd, 0x65, 0xb8, 0x65, 0x1b, 0xba, 0xf4, 0x50, 0x33, 0x8f, 0xa9, 0xcf, 0xb5, 0x3e, 0x3b, 0x3d, 0x3e, 0x14, 0x26, 0xe2, 0xde, 0x79, 0x5c, 0x63, 0xef, 0x30, 0xc1, 0xfa, 0x05, 0x3f, 0xce, 0x84, 0xdb, 0x15, 0x96, 0xe4, 0x33, 0x2d, 0x93, 0x82, 0x62, 0xb3, 0x44, 0x27, 0x8c, 0x31, 0x23, 0xf4, 0x06, 0xb1, 0xe7, 0x01, 0x64, 0x20, 0x63, 0x1e, 0xd4, 0x21, 0x4a, 0xdd, 0x08, 0x0d, 0x34, 0xd3, 0x20, 0x29, 0xe8, 0x70, 0xc0, 0xd0, 0x08, 0xbe, 0x0f, 0xca, 0x28, 0xa4, 0x69, 0xd0, 0xf4, 0x65, 0x38, 0xa7, 0x67, 0xda, 0xb6, 0xb0, 0x83, 0x07, 0xae, 0x45, 0x2e, 0x79, 0xa0, 0x2c, 0x3c, 0x0f, 0x6a, 0x5e, 0x28, 0x01, 0x57, 0x83, 0x4e, 0xf6, 0xa4, 0x34, 0x28, 0xa5, 0x07, 0x9e, 0x29, 0x00, 0xe5, 0xa5, 0x3d, 0xe9, 0x0c, 0x4c, 0xe0, 0xd2, 0x83, 0x40, 0x0d, 0x61, 0xf5, 0xa6, 0x67, 0xd2, 0x9a, 0x00, 0xe2, 0x93, 0x34, 0x08, 0x33, 0xcd, 0x3a, 0x90, 0x01, 0x34, 0x71, 0xc7, 0x34, 0x0c, 0x4a, 0x3a, 0xd2, 0x18, 0xd0, 0x79, 0xa5, 0x3e, 0xff, 0x00, 0xa5, 0x20, 0x1a, 0x69, 0x87, 0x19, 0xe6, 0x81, 0x95, 0x2f, 0xac, 0x6d, 0x2f, 0x90, 0x2d, 0xd4, 0x09, 0x26, 0x06, 0x03, 0x1e, 0x18, 0x7d, 0x01, 0x08, 0xe4, 0x57, 0x1d, 0xa9, 0xee, 0xd3, 0xb5, 0x49, 0x61, 0x8a, 0x57, 0x90, 0x44, 0x15, 0x10, 0xca, 0x77, 0x10, 0xa5, 0x41, 0xc7, 0xb6, 0x37, 0x62, 0x95, 0x89, 0x96, 0x9b, 0x14, 0xce, 0xa1, 0x75, 0x96, 0x2b, 0x22, 0xae, 0x7f, 0xba, 0x8b, 0xf9, 0x72, 0x2a, 0x16, 0x9e, 0x76, 0x39, 0x69, 0xa4, 0xfc, 0x18, 0x8a, 0x64, 0x36, 0x20, 0x57, 0x90, 0xe0, 0xb6, 0x49, 0xfe, 0xf1, 0xa8, 0xe5, 0x8c, 0xc6, 0xd8, 0x24, 0x1f, 0xa5, 0x2b, 0x85, 0xb4, 0x21, 0x34, 0xca, 0x64, 0x85, 0x38, 0x75, 0xa0, 0x0b, 0xf0, 0x9c, 0xc6, 0x29, 0xc6, 0x91, 0xa7, 0x43, 0xd1, 0x50, 0x7a, 0x53, 0x80, 0x3b, 0xab, 0x72, 0x47, 0xf5, 0x14, 0x87, 0xaf, 0xad, 0x20, 0x14, 0x7b, 0x52, 0x9a, 0x06, 0x0b, 0xc5, 0x3b, 0x3f, 0x4a, 0x40, 0x28, 0x3c, 0x73, 0x4e, 0x1d, 0xbd, 0x68, 0x18, 0xe1, 0xc1, 0xa7, 0x1a, 0x40, 0x26, 0x39, 0xa0, 0x0f, 0x5a, 0x00, 0x71, 0x19, 0x5a, 0x85, 0xba, 0xe6, 0x92, 0x18, 0xcc, 0xf3, 0xd6, 0x97, 0xb5, 0x31, 0x07, 0x18, 0xa3, 0x9e, 0xc2, 0x80, 0x1d, 0x9a, 0x09, 0xe2, 0x90, 0xc4, 0xcf, 0x3d, 0x68, 0xef, 0x49, 0x8c, 0x4c, 0xf3, 0xf4, 0xa4, 0x26, 0x90, 0x21, 0x0f, 0x4a, 0x6e, 0x05, 0x03, 0x1a, 0xe4, 0x62, 0xb8, 0x3f, 0x10, 0x48, 0x25, 0xd5, 0xee, 0x88, 0xc7, 0x12, 0x6d, 0xff, 0x00, 0xbe, 0x54, 0x2f, 0xfe, 0xcb, 0x40, 0xa5, 0xb1, 0x97, 0x9a, 0x51, 0x41, 0x9a, 0x24, 0x42, 0x01, 0xa8, 0xe7, 0x6d, 0xcf, 0x48, 0x3a, 0x15, 0xcf, 0x5a, 0x6d, 0x32, 0x44, 0xa5, 0x06, 0x80, 0x2e, 0xdb, 0x1f, 0xdd, 0xd4, 0x8c, 0x78, 0xa5, 0x62, 0xd6, 0xc7, 0xa4, 0x2e, 0x78, 0x15, 0x20, 0x03, 0xad, 0x6c, 0x21, 0x71, 0x8a, 0x61, 0xeb, 0xc5, 0x03, 0x14, 0x0e, 0x79, 0xa0, 0xf5, 0xc5, 0x7e}, 140 | {0x7e, 0x08, 0x01, 0x23, 0x00, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x3a, 0x00, 0x0a, 0x00, 0x08, 0xe0, 0x50, 0x00, 0x3d, 0x69, 0xc3, 0x91, 0x48, 0x07, 0x8a, 0x75, 0x03, 0x00, 0x73, 0xcd, 0x2e, 0x49, 0xa4, 0x02, 0x81, 0x9a, 0x4c, 0xe3, 0x8a, 0x10, 0x0e, 0xcf, 0x1c, 0xd3, 0x1c, 0x11, 0x49, 0x0c, 0x8f, 0x1e, 0xf4, 0xd2, 0x71, 0x4c, 0x41, 0x9e, 0x69, 0x73, 0x40, 0x20, 0x0d, 0x9e, 0x28, 0xc8, 0xcd, 0x03, 0x02, 0xe0, 0x53, 0x77, 0x13, 0xd2, 0x90, 0x0b, 0x83, 0x9a, 0x71, 0x15, 0x23, 0x10, 0x8f, 0x6a, 0x61, 0xe9, 0x40, 0xd0, 0xc6, 0xe4, 0x81, 0xd2, 0xbc, 0xef, 0x52, 0x71, 0x26, 0xa1, 0x72, 0xeb, 0xd1, 0xa7, 0x90, 0x8f, 0xfb, 0xec, 0xd3, 0x26, 0x5b, 0x15, 0x29, 0xb9, 0xa4, 0x41, 0x2c, 0x43, 0x22, 0x99, 0x38, 0xc3, 0x50, 0x0f, 0x62, 0xb9, 0x34, 0x94, 0x12, 0x25, 0x02, 0x80, 0x2d, 0xda, 0x9e, 0x08, 0xa9, 0xcf, 0xa5, 0x22, 0xd6, 0xc7, 0xa4, 0xaf, 0x4a, 0x70, 0x27, 0xb5, 0x6c, 0xc0, 0x77, 0x51, 0xcd, 0x37, 0x23, 0x9a, 0x00, 0x33, 0x49, 0xdf, 0x02, 0x80, 0x1c, 0xa2, 0x9e, 0x01, 0xcd, 0x20, 0x1e, 0x07, 0xad, 0x35, 0xb9, 0xc8, 0xed, 0xed, 0x48, 0x63, 0x87, 0x02, 0x96, 0x80, 0x0f, 0xc6, 0x90, 0x0e, 0x7d, 0x01, 0xe8, 0x18, 0xec, 0xe3, 0xa8, 0xe2, 0x99, 0x24, 0xc8, 0x38, 0x2d, 0x4a, 0xc0, 0x42, 0x5d, 0x4f, 0x39, 0x18, 0xfc, 0xf3, 0x46, 0xe0, 0x7b, 0xfe, 0x54, 0xc4, 0x1d, 0xe9, 0x40, 0xe2, 0x80, 0x41, 0x4c, 0x7c, 0xe0, 0x62, 0x90, 0xd0, 0x88, 0x0f, 0x7a, 0x90, 0x28, 0xa4, 0x03, 0x80, 0xe4, 0x8a, 0x52, 0x68, 0x63, 0x1b, 0x8e, 0xbe, 0x95, 0x19, 0xe3, 0xa5, 0x20, 0x1b, 0xbb, 0x69, 0xdc, 0x7a, 0x0e, 0x4d, 0x79, 0x8e, 0x7f, 0x76, 0x83, 0xfd, 0x91, 0x41, 0x32, 0x12, 0x99, 0x41, 0x05, 0x88, 0x07, 0xcb, 0x51, 0xdc, 0xe7, 0x75, 0x2e, 0xa3, 0x7b, 0x15, 0x8d, 0x25, 0x32, 0x44, 0xa0, 0x50, 0x05, 0x9b, 0x53, 0xf3, 0x62, 0xad, 0x1e, 0xb5, 0x25, 0xad, 0x8f, 0x48, 0x43, 0xc5, 0x2b, 0x67, 0x3c, 0x56, 0xe0, 0x2e, 0xee, 0x31, 0x4d, 0xcf, 0x6a, 0x40, 0x38, 0x0c, 0xf6, 0xa7, 0x28, 0xe7, 0x8a, 0x02, 0xc3, 0x80, 0xa7, 0x72, 0x4d, 0x03, 0x24, 0xc7, 0x15, 0x1b, 0x01, 0x9e, 0xf4, 0x80, 0x31, 0xc7, 0xb8, 0xa5, 0x1d, 0xb8, 0xc9, 0xa0, 0x07, 0x01, 0xbb, 0xd2, 0x82, 0x30, 0x29, 0x01, 0x9f, 0x7b, 0x3b, 0x2f, 0x0a, 0x08, 0xac, 0xc6, 0xf3, 0xa4, 0x6e, 0xa4, 0x9f, 0x6a, 0x4d, 0x94, 0x5e, 0xb5, 0x85, 0xf6, 0xfe, 0xf2, 0xae, 0x2a, 0x63, 0x91, 0x4d, 0x09, 0xee, 0x3b, 0x14, 0x87, 0xa5, 0x31, 0x5c, 0x07, 0x4a, 0x30, 0x3b, 0xd2, 0x00, 0xe9, 0x4a, 0x39, 0xa0, 0x63, 0xb9, 0x14, 0x1e, 0x9d, 0x29, 0x0d, 0x0c, 0x27, 0x27, 0x14, 0xd3, 0xd7, 0xa7, 0x4a, 0x43, 0x29, 0xea, 0x6e, 0x63, 0xd3, 0x2f, 0x1c, 0x1c, 0x15, 0xb7, 0x90, 0x83, 0xef, 0xb4, 0xe2, 0xbc, 0xea, 0x4c, 0x6e, 0xc0, 0xe8, 0x38, 0x14, 0x11, 0x31, 0x3b, 0x53, 0x28, 0x20, 0xb7, 0x6e, 0x3e, 0x5a, 0x86, 0xec, 0x7c, 0xf4, 0x96, 0xe5, 0x3d, 0x8a, 0xa6, 0x92, 0x99, 0x02, 0x51, 0x40, 0x13, 0x5b, 0x9c, 0x3d, 0x5c, 0x26, 0x91, 0x71, 0x67, 0xa5, 0xa8, 0xc2, 0xd2, 0x11, 0x9e, 0x6b, 0x60, 0x43, 0x69, 0xeb, 0xc9, 0xa0, 0x43, 0xa9, 0x57, 0x3e, 0x94, 0x0c, 0x70, 0x1e, 0xb4, 0xec, 0x60, 0x64, 0x8a, 0x43, 0x14, 0x9f, 0xca, 0x8e, 0xfc, 0xd2, 0x01, 0xa4, 0x1f, 0xaf, 0xd2, 0x97, 0x3d, 0x38, 0xa0, 0x09, 0x07, 0xe8, 0x29, 0x31, 0xda, 0x90, 0x11, 0x49, 0x0a, 0xbf, 0xf0, 0xd4, 0x62, 0xdd, 0x57, 0x95, 0x5c, 0x51, 0x61, 0x8e, 0x03, 0x1d, 0x05, 0x1f, 0x8d, 0x31, 0x05, 0x25, 0x0c, 0x04, 0xc6, 0x68, 0xc7, 0xa5, 0x01, 0x60, 0xfc, 0x29, 0xd8, 0xe3, 0xb5, 0x21, 0x8a, 0x38, 0xa3, 0xad, 0x21, 0x8d, 0x34, 0xda, 0x00, 0xcb, 0xf1, 0x1b, 0x32, 0xe8, 0x57, 0x45, 0x4e, 0x09, 0x0a, 0x99, 0xf6, 0x66, 0x50, 0x7f, 0x42, 0x6b, 0x81, 0x73, 0xf3, 0x92, 0x3d, 0x68, 0x26, 0x43, 0x69, 0x00, 0xe6, 0x91, 0x06, 0xa4, 0x50, 0x1f, 0x21, 0x1d, 0x57, 0xaa, 0x83, 0x8a, 0xa1, 0x79, 0xfe, 0xb2, 0xa2, 0x2f, 0x53, 0x49, 0x2b, 0x22, 0xa9, 0xeb, 0x4d, 0xab, 0x32, 0x10, 0xd1, 0x40, 0x12, 0x43, 0xf7, 0xc5, 0x5e, 0xa0, 0xa4, 0x7a, 0x59, 0xe9, 0x4b, 0xdb, 0x9a, 0xd4, 0x62, 0x63, 0x23, 0x8a, 0x70, 0x00, 0x62, 0x80, 0x1d, 0xdf, 0x9a, 0x07, 0x5a, 0x06, 0x38, 0x74, 0xe3, 0xa0, 0xa5, 0x03, 0x8a, 0x40, 0x27, 0xd2, 0x90, 0x66, 0x81, 0x21, 0x73, 0x8a, 0x51, 0xd7, 0x14, 0x8a, 0x26, 0x51, 0xc5, 0x07, 0xbf, 0xa5, 0x48, 0x0d, 0xa4, 0x61, 0xef, 0x4c, 0x06, 0x1f, 0xc2, 0x98, 0x6a, 0x84, 0x25, 0x04, 0x60, 0x52, 0x00, 0x03, 0xbf, 0x5c, 0xfa, 0x52, 0x6d, 0xa0, 0x05, 0x00, 0x75, 0x26, 0x97, 0x9a, 0x06, 0x26, 0x4f, 0xad, 0x2e, 0x38, 0xa4, 0x03, 0x4e, 0x47, 0x7a, 0x69, 0x26, 0x90, 0xcc, 0x3f, 0x16, 0xc8, 0x53, 0x48, 0x54, 0x53, 0xfe, 0xb2, 0x74, 0x53, 0xf4, 0x00, 0xb7, 0xf3, 0x51, 0x5c, 0x4b, 0x0e, 0x68, 0x22, 0x5b, 0x80, 0x1c, 0x53, 0xe3, 0x8c, 0x33, 0x80, 0x4f, 0x7a, 0x42, 0x48, 0xd9, 0xb7, 0x1b, 0x62, 0x0a, 0x3a, 0x0e, 0x05, 0x65, 0xea, 0x8a, 0x05, 0xc1, 0x23, 0xbd, 0x44, 0x77, 0x34, 0x9a, 0xf7, 0x4c, 0xf3, 0x4d, 0xab, 0x31, 0x0c, 0x52, 0x62, 0x80, 0x1f, 0x1f, 0xdf, 0x15, 0xa1, 0xfc, 0x22, 0x91, 0x71, 0x3d, 0x29, 0xe1, 0x7e}, 141 | {0x7e, 0x08, 0x01, 0x23, 0x00, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x3b, 0x00, 0x0a, 0x00, 0x09, 0x79, 0xe3, 0xa5, 0x3f, 0x19, 0xc5, 0x6e, 0x00, 0x57, 0x14, 0x01, 0xc8, 0x3e, 0x94, 0x80, 0x77, 0x51, 0x47, 0x7a, 0x06, 0x2f, 0x7a, 0x33, 0x48, 0x06, 0x93, 0x9a, 0x51, 0x40, 0x0e, 0xe7, 0x14, 0xe5, 0x1c, 0xe6, 0x90, 0x0f, 0xcf, 0x6a, 0x5c, 0x8c, 0x62, 0xa4, 0x63, 0x49, 0x00, 0xe0, 0x54, 0x64, 0xf3, 0x8a, 0x68, 0x06, 0xf4, 0x14, 0x99, 0x26, 0xa8, 0x57, 0x0f, 0xc2, 0x81, 0xf4, 0xa0, 0x03, 0x00, 0xf5, 0x14, 0x75, 0x14, 0x80, 0x4c, 0xf3, 0x8e, 0xf4, 0xa4, 0x66, 0x90, 0xd0, 0x9c, 0x62, 0x97, 0x3e, 0x94, 0x98, 0x0c, 0x6e, 0x05, 0x37, 0x3c, 0x73, 0x45, 0x86, 0x73, 0x5e, 0x30, 0x90, 0x84, 0xb3, 0x88, 0x7d, 0x01, 0xd6, 0x67, 0x73, 0xf5, 0x00, 0x01, 0xff, 0x00, 0xa1, 0x1a, 0xe5, 0x47, 0x5a, 0x44, 0x49, 0xea, 0x3a, 0x91, 0x7a, 0x8a, 0x03, 0xa9, 0xb9, 0x6e, 0x33, 0x1a, 0xfd, 0x05, 0x65, 0x6a, 0xbf, 0xf1, 0xf1, 0xf8, 0x54, 0x47, 0x73, 0x49, 0xfc, 0x26, 0x71, 0xa6, 0xd5, 0x98, 0x85, 0x25, 0x02, 0x1c, 0xbf, 0x78, 0x56, 0x90, 0xc1, 0x40, 0x71, 0x49, 0x97, 0x13, 0xd1, 0xd6, 0xa5, 0xe7, 0x15, 0xb8, 0x0b, 0x8a, 0x41, 0xc5, 0x03, 0x1e, 0x3d, 0x7a, 0x51, 0x83, 0x8a, 0x90, 0x05, 0xce, 0x68, 0x22, 0x80, 0x1b, 0x8c, 0x7f, 0xfa, 0xa9, 0xca, 0xa3, 0xa8, 0xa0, 0x05, 0x14, 0xf0, 0x06, 0x3a, 0xe6, 0xa4, 0x06, 0x93, 0x8e, 0x9c, 0x50, 0x0d, 0x31, 0x89, 0xc9, 0x3d, 0x29, 0xac, 0x0d, 0x02, 0x10, 0xd3, 0x69, 0x88, 0x01, 0xe3, 0x34, 0xee, 0xa3, 0xfa, 0x50, 0x30, 0x14, 0x11, 0x40, 0x58, 0x41, 0xcd, 0x21, 0xa4, 0x30, 0xf6, 0xa0, 0x93, 0x48, 0x68, 0x69, 0x23, 0xbd, 0x37, 0xa0, 0xc0, 0xa4, 0x07, 0x23, 0xe2, 0xd9, 0x18, 0xea, 0x11, 0x46, 0x7d, 0x02, 0xea, 0x40, 0x08, 0xfa, 0xb3, 0x1c, 0xff, 0x00, 0xe8, 0x22, 0xb0, 0x14, 0x50, 0x43, 0xdc, 0x52, 0x29, 0x55, 0x79, 0x14, 0x81, 0x1b, 0x36, 0xff, 0x00, 0xea, 0xc7, 0xd2, 0xb2, 0xb5, 0x33, 0x99, 0x8d, 0x44, 0x77, 0x34, 0x9e, 0xc6, 0x79, 0x14, 0xd3, 0x56, 0x60, 0x25, 0x14, 0x00, 0x0e, 0xb5, 0xa2, 0x8d, 0xfb, 0xb1, 0x49, 0x95, 0x13, 0xd2, 0xe3, 0x00, 0x54, 0x9c, 0x56, 0xc3, 0x17, 0x39, 0x1d, 0x29, 0xb8, 0xa0, 0x09, 0x17, 0x1d, 0xea, 0x4c, 0x64, 0x71, 0xc5, 0x26, 0x31, 0x36, 0xe3, 0x9a, 0x69, 0xeb, 0x48, 0x06, 0xd2, 0xe7, 0x1d, 0xa9, 0x80, 0x13, 0x9e, 0x39, 0x34, 0xa0, 0x91, 0xee, 0x29, 0x58, 0x00, 0x60, 0xd0, 0x3b, 0x71, 0xde, 0x90, 0x0a, 0x00, 0xfa, 0xd3, 0x5a, 0x98, 0x0d, 0x6a, 0x69, 0x1d, 0x85, 0x00, 0x1e, 0x94, 0xa0, 0x53, 0x00, 0xed, 0x49, 0xef, 0x40, 0x09, 0xc7, 0x5c, 0x51, 0xd6, 0x90, 0xc4, 0x04, 0x67, 0x8a, 0x2a, 0x41, 0x0c, 0x6a, 0x6e, 0xe3, 0xeb, 0x40, 0xd1, 0xc4, 0x78, 0x8d, 0xf7, 0x6b, 0x57, 0x43, 0x39, 0xd9, 0xb1, 0x47, 0xe0, 0x83, 0xfa, 0x93, 0x59, 0xa9, 0x41, 0x0c, 0x76, 0x05, 0x3d, 0x00, 0xdc, 0x29, 0x02, 0x35, 0x23, 0x18, 0x50, 0x2b, 0x27, 0x50, 0xe6, 0x72, 0x3d, 0x2a, 0x62, 0x69, 0x2d, 0x8a, 0x2d, 0x4d, 0x35, 0x46, 0x02, 0x11, 0x49, 0x8a, 0x00, 0x3b, 0xd5, 0xf8, 0x88, 0xf2, 0xc5, 0x0c, 0xa4, 0x7a, 0x62, 0x36, 0x40, 0x22, 0xa4, 0x07, 0x23, 0x35, 0xb0, 0xc5, 0x5c, 0x9a, 0x5c, 0x67, 0xb5, 0x21, 0x8a, 0x38, 0x38, 0xe6, 0x9e, 0xad, 0xf4, 0xa4, 0x03, 0xb3, 0xc5, 0x34, 0xf3, 0x48, 0x03, 0x1c, 0x51, 0xb7, 0xd2, 0x80, 0x0d, 0xbc, 0xf4, 0xa5, 0x2b, 0xdf, 0xad, 0x20, 0x1b, 0x4e, 0xe9, 0xcd, 0x00, 0x21, 0x6f, 0xad, 0x46, 0x4f, 0x3c, 0x9c, 0xfd, 0x28, 0x01, 0x40, 0xe3, 0x9c, 0xd3, 0x71, 0x4c, 0x05, 0x0b, 0x49, 0x8e, 0x7a, 0xd0, 0x02, 0x7b, 0x67, 0x14, 0x50, 0xc2, 0xe2, 0x1e, 0x45, 0x34, 0xd0, 0x01, 0x9f, 0x6a, 0x43, 0x9a, 0x43, 0x43, 0x4f, 0x4e, 0x69, 0xa4, 0xf6, 0x14, 0x8a, 0x38, 0x0d, 0x4d, 0xfc, 0xcd, 0x46, 0xe9, 0xf7, 0x6e, 0xcc, 0xef, 0xce, 0x7a, 0x80, 0xc4, 0x0f, 0xd0, 0x0a, 0xac, 0xb4, 0x19, 0x8a, 0x4e, 0x29, 0xea, 0xe0, 0x30, 0xc5, 0x20, 0x46, 0xba, 0x82, 0x05, 0x63, 0x5f, 0x9f, 0xf4, 0x86, 0xa9, 0x89, 0xa4, 0xf6, 0x29, 0x1e, 0xb4, 0x86, 0xa8, 0xc4, 0x6d, 0x25, 0x02, 0x01, 0xd6, 0xae, 0x43, 0xf7, 0x05, 0x22, 0x91, 0xe8, 0xf6, 0xd2, 0x8c, 0x0c, 0x9e, 0x2a, 0xe2, 0xe3, 0x6e, 0x71, 0x5b, 0x8c, 0x70, 0x1d, 0xe9, 0xc0, 0xfd, 0x6a, 0x40, 0x5e, 0x0f, 0x14, 0xa3, 0xe9, 0x40, 0xc3, 0xbd, 0x2e, 0x38, 0xa4, 0x02, 0x9e, 0x31, 0x47, 0x00, 0x52, 0x00, 0xf4, 0xa4, 0x27, 0xb5, 0x00, 0x27, 0xf5, 0xa4, 0xdd, 0xda, 0x98, 0x21, 0xa7, 0x14, 0x98, 0x04, 0xd0, 0x31, 0xc4, 0x1e, 0x94, 0x98, 0xf4, 0x03, 0x3e, 0xd4, 0x00, 0xe0, 0x31, 0xd6, 0x9a, 0xc3, 0x1d, 0x29, 0x08, 0x6f, 0x5a, 0x4f, 0xd6, 0xa8, 0x06, 0xfe, 0x94, 0x94, 0x80, 0x00, 0xa5, 0x3d, 0x31, 0x48, 0xa1, 0x8c, 0x31, 0xe8, 0x6a, 0x36, 0x70, 0x87, 0x7b, 0x72, 0x17, 0x93, 0x8f, 0x41, 0x48, 0x0f, 0x36, 0xe9, 0x12, 0x03, 0xfd, 0xd1, 0x40, 0x3c, 0x50, 0x66, 0x05, 0xb1, 0x52, 0x5b, 0x61, 0xa6, 0x55, 0x3d, 0x09, 0xa4, 0xf6, 0x1a, 0xdc, 0xdf, 0x89, 0x03, 0xb2, 0xa1, 0x38, 0xcf, 0xa7, 0x5c, 0x77, 0xac, 0x1d, 0x54, 0xa7, 0xdb, 0x64, 0xf2, 0xd7, 0x6a, 0x6e, 0x3b, 0x46, 0x73, 0x8f, 0xc6, 0xb3, 0x8b, 0x6b, 0x7e}, 142 | {0x7e, 0x08, 0x01, 0x20, 0x17, 0x01, 0x38, 0x12, 0x34, 0x56, 0x78, 0x20, 0x3c, 0x00, 0x0a, 0x00, 0x0a, 0xd4, 0xde, 0xa2, 0xf7, 0x2e, 0x50, 0x26, 0x9b, 0x5a, 0x1c, 0xa2, 0x51, 0x40, 0x0a, 0x2a, 0xd4, 0x27, 0xe5, 0xa4, 0xca, 0x47, 0xff, 0xd9, 0xd1, 0x7e}, 143 | } 144 | 145 | tests := []struct { 146 | name string 147 | mesgDataList [][]byte 148 | assertFunc func(t *testing.T, mesgPack *message.MessagePack[message.MesgBody]) 149 | wantErr bool 150 | }{ 151 | { 152 | name: "incomplete message pack", 153 | mesgDataList: mesgDataList[0:3], 154 | wantErr: true, 155 | }, 156 | { 157 | name: "unmarshal success", 158 | mesgDataList: mesgDataList[:], 159 | assertFunc: func(t *testing.T, mesgPack *message.MessagePack[message.MesgBody]) { 160 | if assert.IsType(t, new(message.Body0801), mesgPack.PackBody) { 161 | packBody := mesgPack.PackBody.(*message.Body0801) 162 | 163 | assert.Equal(t, byte(0x00), packBody.MediaType) 164 | assert.Equal(t, byte(0x00), packBody.MediaContentType) 165 | 166 | mediaContentReader := bytes.NewReader(packBody.MediaContent) 167 | mediaContent, err := jpeg.Decode(mediaContentReader) 168 | 169 | if assert.NoError(t, err) { 170 | assert.Equal(t, 320, mediaContent.Bounds().Size().X) 171 | assert.Equal(t, 240, mediaContent.Bounds().Size().Y) 172 | } 173 | } 174 | 175 | }, 176 | wantErr: false, 177 | }, 178 | } 179 | 180 | for _, tt := range tests { 181 | t.Run(tt.name, func(t *testing.T) { 182 | mesgPacks := make([]*message.MessagePack[*message.PartialPackBody], len(tt.mesgDataList)) 183 | 184 | for i, mesgData := range tt.mesgDataList { 185 | mesgPack := new(message.MessagePack[*message.PartialPackBody]) 186 | 187 | err := mesgPack.UnmarshalBinary(mesgData) 188 | 189 | if !assert.NoError(t, err) { 190 | t.FailNow() 191 | } 192 | 193 | mesgPacks[i] = mesgPack 194 | } 195 | 196 | mesgPack := new(message.MessagePack[message.MesgBody]) 197 | err := ConcatUnmarshal(mesgPacks[:], mesgPack) 198 | 199 | if tt.wantErr { 200 | assert.Error(t, err) 201 | } else if assert.NoError(t, err) { 202 | tt.assertFunc(t, mesgPack) 203 | } 204 | }) 205 | } 206 | } 207 | --------------------------------------------------------------------------------