├── go.mod
├── .gitignore
├── LICENSE
├── examples
├── test-client
│ └── main.go
├── README.md
├── simple-udp-server
│ └── main.go
└── simple-tcp-server
│ └── main.go
├── tools
├── README.md
└── io_elements_gen.go
├── util.go
├── ioelements
└── ioelements.go
├── README.md
├── teltonika_test.go
└── teltonika.go
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/alim-zanibekov/teltonika
2 |
3 | go 1.18
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .*/
2 |
3 | *.exe
4 | *.exe~
5 | *.dll
6 | *.so
7 | *.dylib
8 |
9 | *.test
10 |
11 | *.out
12 |
13 | go.work
14 |
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Alim Zanibekov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/examples/test-client/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Alim Zanibekov
2 | //
3 | // Use of this source code is governed by an MIT-style
4 | // license that can be found in the LICENSE file or at
5 | // https://opensource.org/licenses/MIT.
6 |
7 | package main
8 |
9 | import (
10 | "bufio"
11 | "encoding/hex"
12 | "flag"
13 | "fmt"
14 | "net"
15 | "os"
16 | "strings"
17 | )
18 |
19 | func main() {
20 | var address string
21 | var mode string
22 | flag.StringVar(&address, "address", "0.0.0.0:8080", "server address")
23 | flag.StringVar(&mode, "mode", "tcp", "tcp or udp")
24 | flag.Parse()
25 |
26 | conn, err := net.Dial(mode, address)
27 | if err != nil {
28 | fmt.Printf("error initializing connection (%v)\n", err)
29 | os.Exit(1)
30 | }
31 |
32 | defer func(conn net.Conn) {
33 | err := conn.Close()
34 | if err != nil {
35 | fmt.Printf("error closing connection (%v)\n", err)
36 | os.Exit(1)
37 | }
38 | }(conn)
39 |
40 | fmt.Print("enter 'e' to exit\n")
41 |
42 | reader := bufio.NewReader(os.Stdin)
43 | res := make([]byte, 1000)
44 | for {
45 | fmt.Print("hex: ")
46 | raw, _ := reader.ReadString('\n')
47 | str := strings.Trim(raw, "\n ")
48 |
49 | if str == "e" {
50 | break
51 | }
52 |
53 | if str == "ping" {
54 | if mode != "tcp" {
55 | fmt.Printf("ping is only supported in tcp mode\n")
56 | continue
57 | }
58 | if _, err := conn.Write([]byte{0xFF}); err != nil {
59 | fmt.Printf("send error (%v)\n", err)
60 | return
61 | }
62 | fmt.Printf("send ping (0xFF)\n")
63 | continue
64 | }
65 |
66 | packet, err := hex.DecodeString(str)
67 | if err != nil {
68 | fmt.Printf("unable to decode hex input (%v)\n", err)
69 | continue
70 | }
71 |
72 | if _, err := conn.Write(packet); err != nil {
73 | fmt.Printf("send error (%v)\n", err)
74 | return
75 | }
76 |
77 | n, err := conn.Read(res)
78 | if err != nil {
79 | fmt.Printf("read error (%v)\n", err)
80 | return
81 | }
82 |
83 | fmt.Printf("server response: %s\n", hex.EncodeToString(res[:n]))
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples of using the `teltonika` library
2 |
3 | `simple-tcp-server` and `simple-tcp-server` - TCP/UDP servers for test purposes,
4 | the server processes tracker messages received through the network,
5 | decodes them and sends to the hook (`-hook` command line arg) in json format (`SimplePacket` struct, see sources)
6 |
7 | `test-client` - a simple client to simulate the sending of data by the tracker,
8 | accepts data via `stdin` in `hex` format
9 |
10 | ```shell
11 | go build -o tcp-server simple-tcp-server/main.go
12 | go build -o udp-server simple-udp-server/main.go
13 | go build -o client test-client/main.go
14 | ```
15 |
16 | Run server
17 |
18 | ```shell
19 | ./tcp-server -h # help
20 | ./tcp-server -address '127.0.0.1:8080' -http '127.0.0.1:8081'
21 | ```
22 |
23 | Run client
24 |
25 | ```shell
26 | ./client -h # help
27 | ./client -address '127.0.0.1:8080' -mode tcp # mode: tcp or udp
28 | ```
29 |
30 | Output
31 |
32 | ```text
33 | enter 'e' to exit
34 | hex:
35 | ```
36 |
37 | Input a message (as hex string)
38 |
39 | ```text
40 | enter 'e' to exit
41 | hex: 000f333534303137313138383035373138
42 | server response: 01
43 | hex: 000000000000003608010000016B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000010000C7CF
44 | server response: 00000001
45 | hex:
46 | ```
47 |
48 | Server logs
49 |
50 | ```text
51 | INFO: 2022/07/10 10:29:53 http server listening at 127.0.0.1:8081
52 | INFO: 2022/07/10 10:29:53 tcp server listening at 127.0.0.1:8080
53 | INFO: 2022/07/10 10:30:08 [127.0.0.1:53840]: connected
54 | INFO: 2022/07/10 10:31:32 [127.0.0.1:53840]: imei - 354017118805718
55 | INFO: 2022/07/10 10:31:57 [354017118805718]: message: 000000000000003608010000016b40d8ea30010000000000000000000000000000000105021503010101425e0f01f10000601a014e0000000000000000010000c7cf
56 | INFO: 2022/07/10 10:31:57 [354017118805718]: decoded: {"codecId":8,"data":[{"timestampMs":1560161086000,"lng":0,"lat":0,"altitude":0,"angle":0,"event_id":1,"speed":0,"satellites":0,"priority":1,"generationType":255,"elements":[{"id":21,"value":"Aw=="},{"id":1,"value":"AQ=="},{"id":66,"value":"Xg8="},{"id":241,"value":"AABgGg=="},{"id":78,"value":"AAAAAAAAAAA="}]}]}
57 | ```
58 |
59 | ---
60 |
61 | TCP server also supports sending commands to the connected tracker
62 |
63 | List connected trackers
64 |
65 | ```bash
66 | curl "http://localhost:8081/list-clients"
67 | ```
68 |
69 | HTTP response (addr - imei)
70 |
71 | ```text
72 | 127.0.0.1:62548 - 354017118805718
73 | ```
74 |
75 | Send `deleterecords` command (for
76 | example [FMB125 command list](https://wiki.teltonika-gps.com/view/FMB125_SMS/GPRS_Commands)):
77 |
78 | ```bash
79 | curl "http://localhost:8081/cmd?imei=354017118805718" -d "deleterecords"
80 | ```
81 |
82 | HTTP response: `All records are erased`
83 |
84 | Server logs
85 |
86 | ```text
87 | INFO: 2022/08/02 15:58:30 command deleterecords sent to 354017118805718
88 | ...
89 | INFO: 2022/08/02 15:58:44 [354017118805718]: message: 000000000000001e0c010600000016416c6c207265636f7264732061726520657261736564010000bc2a
90 | INFO: 2022/08/02 15:58:44 [354017118805718]: decoded: {"codecId":12,"messages":[{"type":6,"command":"All records are erased"}]}
91 | ```
92 |
--------------------------------------------------------------------------------
/tools/README.md:
--------------------------------------------------------------------------------
1 | `io_elements_gen.go` - IO Elements generator.
2 | Recursively parses descriptions of I/O elements from wiki pages.
3 | by recursively following the links to other models and writes output to csv and go files.
4 |
5 | ```shell
6 | go run io_elements_gen.go -h
7 | go run io_elements_gen.go load-net -h
8 | go run io_elements_gen.go load-csv -h
9 | ```
10 |
11 | ```text
12 | Usage:
13 | io_elements_gen [OPTIONS] load-net [load-net-OPTIONS]
14 | io_elements_gen [OPTIONS] load-csv InputCsvFile
15 |
16 | Application Options:
17 | -o, --gen-out= output file path for I/O elements definitions list (default: ./ioelements_dump.go)
18 | --no-gen disable go file generation
19 | --gen-pkg-name= package name for generated file (default: main)
20 | --gen-internal generate file for internal usage in ioelements package
21 |
22 | Help Options:
23 | -h, --help Show this help message
24 |
25 | Available commands:
26 | load-csv
27 | load-net
28 |
29 | [load-net command options]
30 | -m, --model= models to parse, can be specified multiple times (default: FMB920, FMC650)
31 | --csv-out= csv output file path
32 | --cache= path to http cache file (default: ./cache.bin)
33 | --no-cache disable http cache
34 | --no-follow disable recursive links following
35 | --url-pattern= url generation pattern from specified model names
36 | substring '{model}' will be substituted
37 | (default: https://wiki.teltonika-gps.com/view/{model}_Teltonika_Data_Sending_Parameters_ID)
38 | ```
39 |
40 | You can generate the list yourself and use it via the `ioelements`
41 | package by passing it to the NewDecoder function.
42 |
43 | This command loads a list of I/O element definitions for FMB920 and FMB900 without
44 | following links to other models, and then writes the result to a go file `my_ioelements.go`
45 | with the package name `main`.
46 |
47 | ```shell
48 | go run io_elements_gen.go load-net -m FMB920 -m FMB900 --no-follow -o my_ioelements.go --gen-pkg-name=main
49 | ```
50 |
51 | Example usage
52 | ```go
53 | package main
54 |
55 | import (
56 | "fmt"
57 |
58 | "github.com/alim-zanibekov/teltonika/ioelements"
59 | )
60 |
61 | func main() {
62 | myParser := ioelements.NewDecoder(ioElementDefinitions) // ioElementDefinitions from the generated ./my_ioelements.go
63 | parsed, err := myParser.Decode("*", 1, []byte{1})
64 | if err != nil {
65 | panic(err)
66 | }
67 | fmt.Printf("%s\n", parsed)
68 | }
69 | ```
70 |
71 | Output
72 |
73 | ```text
74 | Digital Input 1: true
75 | ```
76 |
77 | Other examples
78 |
79 | Load FMB920 and FMB900 and write results to the `io_elements_dump.csv`
80 | ```shell
81 | go run io_elements_gen.go load-net -m FMB920 -m FMB900 --no-follow --no-gen --csv-out ./io_elements_dump.csv
82 | ```
83 |
84 | Load data from the `io_elements_dump.csv` file and generate `my_ioelements.go` file
85 | ```shell
86 | go run io_elements_gen.go load-csv ./io_elements_dump.csv -o my_ioelements.go --gen-pkg-name=main
87 | ```
88 |
89 | The way how current `/ioelements/ioelements_dump.go` was generated
90 | ```shell
91 | go run io_elements_gen.go load-net -m FMB920 -m FMC650 -m FMC225 -m FMB225 --csv-out ./io_elements_dump.csv -o ../ioelements/ioelements_dump.go --gen-internal --gen-pkg-name ioelements
92 | ```
--------------------------------------------------------------------------------
/util.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Alim Zanibekov
2 | //
3 | // Use of this source code is governed by an MIT-style
4 | // license that can be found in the LICENSE file or at
5 | // https://opensource.org/licenses/MIT.
6 |
7 | package teltonika
8 |
9 | import (
10 | "encoding/binary"
11 | "io"
12 | )
13 |
14 | func genCrc16IBMLookupTable() []uint16 {
15 | table := make([]uint16, 256)
16 | i := 128
17 | crc := uint16(1)
18 | for i > 0 {
19 | if (crc & 0x0001) == 1 {
20 | crc = (crc >> 1) ^ 0xA001
21 | } else {
22 | crc = crc >> 1
23 | }
24 | for j := 0; j < 255; j += i * 2 {
25 | table[i+j] = crc ^ table[j]
26 | }
27 | i = i >> 1
28 | }
29 | return table
30 | }
31 |
32 | var crc16IBMLookupTable = genCrc16IBMLookupTable()
33 |
34 | func Crc16IBM(data []byte) uint16 {
35 | crc := uint16(0)
36 | size := len(data)
37 | for i := 0; i < size; i++ {
38 | lookupIndex := (crc ^ uint16(data[i])) & 0xff
39 | crc = (crc >> 8) ^ crc16IBMLookupTable[lookupIndex]
40 | }
41 | return crc
42 | }
43 |
44 | type byteReader struct {
45 | pos int
46 | size int
47 | input []byte
48 | allocOnRead bool
49 | }
50 |
51 | func newByteReader(input []byte, allocOnRead bool) *byteReader {
52 | return &byteReader{input: input, size: len(input), allocOnRead: allocOnRead}
53 | }
54 |
55 | func (r *byteReader) ReadBytes(n int) ([]byte, error) {
56 | if r.pos+n > r.size {
57 | return nil, io.EOF
58 | }
59 | r.pos += n
60 |
61 | if r.allocOnRead {
62 | buf := make([]byte, n)
63 | copy(buf, r.input[r.pos-n:r.pos])
64 |
65 | return buf, nil
66 | }
67 |
68 | return r.input[r.pos-n : r.pos], nil
69 | }
70 |
71 | func (r *byteReader) ReadUInt8BE() (uint8, error) {
72 | if r.pos+1 > r.size {
73 | return 0, io.EOF
74 | }
75 | r.pos += 1
76 | return r.input[r.pos-1], nil
77 | }
78 |
79 | func (r *byteReader) ReadInt8BE() (int8, error) {
80 | if r.pos+1 > r.size {
81 | return 0, io.EOF
82 | }
83 | r.pos += 1
84 | return int8(r.input[r.pos-1]), nil
85 | }
86 |
87 | func (r *byteReader) ReadUInt16BE() (uint16, error) {
88 | if r.pos+2 > r.size {
89 | return 0, io.EOF
90 | }
91 | r.pos += 2
92 | return binary.BigEndian.Uint16(r.input[r.pos-2:]), nil
93 | }
94 |
95 | func (r *byteReader) ReadInt16BE() (int16, error) {
96 | if r.pos+2 > r.size {
97 | return 0, io.EOF
98 | }
99 | r.pos += 2
100 | return int16(binary.BigEndian.Uint16(r.input[r.pos-2:])), nil
101 | }
102 |
103 | func (r *byteReader) ReadUInt32BE() (uint32, error) {
104 | if r.pos+4 > r.size {
105 | return 0, io.EOF
106 | }
107 | r.pos += 4
108 | return binary.BigEndian.Uint32(r.input[r.pos-4:]), nil
109 | }
110 |
111 | func (r *byteReader) ReadInt32BE() (int32, error) {
112 | if r.pos+4 > r.size {
113 | return 0, io.EOF
114 | }
115 | r.pos += 4
116 | return int32(binary.BigEndian.Uint32(r.input[r.pos-4:])), nil
117 | }
118 |
119 | func (r *byteReader) ReadUInt64BE() (uint64, error) {
120 | if r.pos+8 > r.size {
121 | return 0, io.EOF
122 | }
123 | r.pos += 8
124 | return binary.BigEndian.Uint64(r.input[r.pos-8:]), nil
125 | }
126 |
127 | func (r *byteReader) ReadInt64BE() (int64, error) {
128 | if r.pos+8 > r.size {
129 | return 0, io.EOF
130 | }
131 | r.pos += 8
132 | return int64(binary.BigEndian.Uint64(r.input[r.pos-8:])), nil
133 | }
134 |
--------------------------------------------------------------------------------
/ioelements/ioelements.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2024 Alim Zanibekov
2 | //
3 | // Use of this source code is governed by an MIT-style
4 | // license that can be found in the LICENSE file or at
5 | // https://opensource.org/licenses/MIT.
6 |
7 | package ioelements
8 |
9 | import (
10 | "encoding/binary"
11 | "encoding/hex"
12 | "fmt"
13 | )
14 |
15 | type ElementType uint8
16 |
17 | const (
18 | IOElementSigned ElementType = iota
19 | IOElementUnsigned
20 | IOElementHEX
21 | IOElementASCII
22 | )
23 |
24 | type IOElementDefinition struct {
25 | Id uint16 `json:"id"`
26 | Name string `json:"name"`
27 | NumBytes int `json:"numBytes"`
28 | Type ElementType `json:"type"`
29 | Min float64 `json:"min"`
30 | Max float64 `json:"max"`
31 | Multiplier float64 `json:"multiplier"`
32 | Units string `json:"units"`
33 | Description string `json:"description"`
34 | SupportedModels []string `json:"supportedModels"`
35 | Groups []string `json:"groups"`
36 | }
37 |
38 | type IOElement struct {
39 | Id uint16 `json:"id,omitempty"`
40 | Value interface{} `json:"value,omitempty"`
41 | Definition *IOElementDefinition `json:"definition,omitempty"`
42 | }
43 |
44 | type Decoder struct {
45 | definitions []IOElementDefinition
46 | supportedModels map[string]bool
47 | }
48 |
49 | var defaultDecoder = &Decoder{ioElementDefinitions, supportedModels}
50 |
51 | func (r *IOElement) String() string {
52 | switch r.Value.(type) {
53 | case float64:
54 | return fmt.Sprintf("%s: %.3f%s", r.Definition.Name, r.Value, r.Definition.Units)
55 | default:
56 | return fmt.Sprintf("%s: %v%s", r.Definition.Name, r.Value, r.Definition.Units)
57 | }
58 | }
59 |
60 | // NewDecoder create new Decoder
61 | func NewDecoder(definitions []IOElementDefinition) *Decoder {
62 | allSupportedModels := map[string]bool{}
63 | for _, it := range definitions {
64 | for _, model := range it.SupportedModels {
65 | allSupportedModels[model] = true
66 | }
67 | }
68 | return &Decoder{definitions, allSupportedModels}
69 | }
70 |
71 | // DefaultDecoder returns a decoder with I/O Element definitions represented in `ioelements_dump.go` file
72 | func DefaultDecoder() *Decoder {
73 | return defaultDecoder
74 | }
75 |
76 | // GetElementInfo returns full description of I/O Element by its id and model name
77 | // If you don't know the model name, you can skip the model name check by passing '*' as the model name
78 | func (r *Decoder) GetElementInfo(modelName string, id uint16) (*IOElementDefinition, error) {
79 | if modelName != "*" && !r.supportedModels[modelName] {
80 | return nil, fmt.Errorf("model '%s' is not supported", modelName)
81 | }
82 |
83 | for _, e := range r.definitions {
84 | if e.Id != id {
85 | continue
86 | }
87 | if modelName == "*" {
88 | return &e, nil
89 | }
90 |
91 | for _, v := range e.SupportedModels {
92 | if v != modelName {
93 | continue
94 | }
95 | return &e, nil
96 | }
97 | }
98 |
99 | return nil, fmt.Errorf("element with id %v not found", id)
100 | }
101 |
102 | // Decode decodes an I/O Element by model name and id (result can be represented in numan-readable format)
103 | // If you don't know the model name, you can skip the model name check by passing '*' as the model name
104 | func (r *Decoder) Decode(modelName string, id uint16, buffer []byte) (*IOElement, error) {
105 | def, err := r.GetElementInfo(modelName, id)
106 | if err != nil {
107 | return nil, err
108 | }
109 | return r.DecodeByDefinition(def, buffer)
110 | }
111 |
112 | // DecodeByDefinition decodes an I/O Element according to a given definition
113 | func (r *Decoder) DecodeByDefinition(def *IOElementDefinition, buffer []byte) (*IOElement, error) {
114 | var res interface{}
115 |
116 | size := len(buffer)
117 | if size == 1 && def.Min == 0 && def.Max == 1 && def.Type == IOElementUnsigned {
118 | res = buffer[0] == 1
119 | } else if (size == 1 || size == 2 || size == 4 || size == 8) && (def.Type == IOElementUnsigned || def.Type == IOElementSigned) {
120 | if def.Type == IOElementUnsigned {
121 | var v uint64
122 | if size == 1 {
123 | v = uint64(buffer[0])
124 | } else if size == 2 {
125 | v = uint64(binary.BigEndian.Uint16(buffer))
126 | } else if size == 4 {
127 | v = uint64(binary.BigEndian.Uint32(buffer))
128 | } else {
129 | v = binary.BigEndian.Uint64(buffer)
130 | }
131 | if def.Multiplier != 1.0 {
132 | res = float64(v) * def.Multiplier
133 | } else {
134 | res = v
135 | }
136 | } else if def.Type == IOElementSigned {
137 | var v int64
138 | if size == 1 {
139 | v = int64(int8(buffer[0]))
140 | } else if size == 2 {
141 | v = int64(int16(binary.BigEndian.Uint16(buffer)))
142 | } else if size == 4 {
143 | v = int64(int32(binary.BigEndian.Uint32(buffer)))
144 | } else {
145 | v = int64(binary.BigEndian.Uint64(buffer))
146 | }
147 | if def.Multiplier != 1.0 {
148 | res = float64(v) * def.Multiplier
149 | } else {
150 | res = v
151 | }
152 | }
153 | } else if def.Type == IOElementHEX {
154 | res = hex.EncodeToString(buffer)
155 | } else if def.Type == IOElementASCII {
156 | res = string(buffer)
157 | }
158 |
159 | if res == nil {
160 | return nil, fmt.Errorf("unable to proceed io element with id %v for buffer '%s'", def.Id, hex.EncodeToString(buffer))
161 | }
162 |
163 | return &IOElement{
164 | def.Id, res, def,
165 | }, nil
166 | }
167 |
--------------------------------------------------------------------------------
/examples/simple-udp-server/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Alim Zanibekov
2 | //
3 | // Use of this source code is governed by an MIT-style
4 | // license that can be found in the LICENSE file or at
5 | // https://opensource.org/licenses/MIT.
6 |
7 | package main
8 |
9 | import (
10 | "bytes"
11 | "encoding/hex"
12 | "encoding/json"
13 | "flag"
14 | "fmt"
15 | "log"
16 | "net"
17 | "net/http"
18 | "os"
19 | "strconv"
20 | "strings"
21 | "time"
22 |
23 | "github.com/alim-zanibekov/teltonika"
24 | "github.com/alim-zanibekov/teltonika/ioelements"
25 | )
26 |
27 | var decodeConfig = &teltonika.DecodeConfig{IoElementsAlloc: teltonika.OnReadBuffer}
28 |
29 | type Logger struct {
30 | Info *log.Logger
31 | Error *log.Logger
32 | }
33 |
34 | type UDPServer struct {
35 | address string
36 | logger *Logger
37 | OnPacket func(imei string, pkt *teltonika.Packet)
38 | workerCount int
39 | }
40 |
41 | //goland:noinspection GoUnusedExportedFunction
42 | func NewUDPServer(address string, workerCount int) *UDPServer {
43 | return &UDPServer{address: address, workerCount: workerCount, logger: &Logger{log.Default(), log.Default()}}
44 | }
45 |
46 | func NewUDPServerLogger(address string, workerCount int, logger *Logger) *UDPServer {
47 | return &UDPServer{address: address, workerCount: workerCount, logger: logger}
48 | }
49 |
50 | func (r *UDPServer) Run() error {
51 | logger := r.logger
52 | hostStr, portStr, err := net.SplitHostPort(r.address)
53 | if err != nil {
54 | return fmt.Errorf("split host and port error (%v)", err)
55 | }
56 | port, err := strconv.Atoi(portStr)
57 | if err != nil {
58 | return fmt.Errorf("port parse error (%v)", err)
59 | }
60 | ip := net.ParseIP(hostStr)
61 | udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: ip, Port: port, Zone: ""})
62 | if err != nil {
63 | return fmt.Errorf("listen udp error (%v)", err)
64 | }
65 |
66 | defer func() {
67 | _ = udpConn.Close()
68 | }()
69 |
70 | logger.Info.Printf("udp listening at %s", hostStr)
71 |
72 | type job struct {
73 | buffer []byte
74 | addr *net.UDPAddr
75 | }
76 |
77 | jobs := make(chan job, r.workerCount)
78 | defer close(jobs)
79 |
80 | worker := func(conn *net.UDPConn) {
81 | for j := range jobs {
82 | r.handleConnection(conn, j.addr, j.buffer)
83 | }
84 | }
85 |
86 | for i := 0; i < r.workerCount; i++ {
87 | go worker(udpConn)
88 | }
89 |
90 | for {
91 | buf := make([]byte, 1300)
92 | n, addr, err := udpConn.ReadFromUDP(buf)
93 | if err != nil {
94 | return fmt.Errorf("udp read packet error (%v)", err)
95 | }
96 |
97 | packet := make([]byte, n)
98 | copy(packet, buf[:n])
99 |
100 | jobs <- job{packet, addr}
101 | }
102 | }
103 |
104 | func (r *UDPServer) handleConnection(conn *net.UDPConn, addr *net.UDPAddr, packet []byte) {
105 | logger := r.logger
106 | client := addr.String()
107 |
108 | _, res, err := teltonika.DecodeUDPFromSlice(packet, decodeConfig)
109 | if err != nil {
110 | logger.Error.Printf("[%s]: packet decode error (%v)", client, err)
111 | return
112 | }
113 |
114 | if res.Response != nil {
115 | if _, err = conn.WriteToUDP(res.Response, addr); err != nil {
116 | logger.Error.Printf("[%s]: error writing response (%v)", client, err)
117 | return
118 | }
119 | }
120 |
121 | logger.Info.Printf("[%s]: message: %s", client, hex.EncodeToString(packet))
122 | jsonData, err := json.Marshal(res.Packet)
123 | if err != nil {
124 | logger.Error.Printf("[%s]: marshaling error (%v)", client, err)
125 | } else {
126 | logger.Info.Printf("[%s]: decoded: %s", client, string(jsonData))
127 | for i, data := range res.Packet.Data {
128 | elements := make([]string, len(data.Elements))
129 | for j, element := range data.Elements {
130 | it, err := ioelements.DefaultDecoder().Decode("*", element.Id, element.Value)
131 | if err != nil {
132 | break
133 | }
134 | elements[j] = it.String()
135 | }
136 | logger.Info.Printf("[%s]: io elements [frame #%d]: %s", client, i, strings.Join(elements, ", "))
137 | }
138 | }
139 |
140 | if r.OnPacket != nil {
141 | r.OnPacket(res.Imei, res.Packet)
142 | }
143 | }
144 |
145 | func main() {
146 | var address string
147 | var outHook string
148 | flag.StringVar(&address, "address", "0.0.0.0:8080", "server address")
149 | flag.StringVar(&outHook, "hook", "", "output hook\nfor example: http://localhost:8080/push")
150 | flag.Parse()
151 |
152 | logger := &Logger{
153 | Info: log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime),
154 | Error: log.New(os.Stdout, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile),
155 | }
156 |
157 | server := NewUDPServerLogger(address, 20, logger)
158 |
159 | server.OnPacket = func(imei string, pkt *teltonika.Packet) {
160 | if pkt.Data != nil && outHook != "" {
161 | go hookSend(outHook, imei, pkt, logger)
162 | }
163 | }
164 |
165 | panic(server.Run())
166 | }
167 |
168 | func buildJsonPacket(imei string, pkt *teltonika.Packet) []byte {
169 | if pkt.Data == nil {
170 | return nil
171 | }
172 | gpsFrames := make([]interface{}, 0)
173 | for _, frame := range pkt.Data {
174 | gpsFrames = append(gpsFrames, map[string]interface{}{
175 | "timestamp": int64(frame.TimestampMs / 1000.0),
176 | "lat": frame.Lat,
177 | "lon": frame.Lng,
178 | })
179 | }
180 | if len(gpsFrames) == 0 {
181 | return nil
182 | }
183 | values := map[string]interface{}{
184 | "deveui": imei,
185 | "time": time.Now().String(),
186 | "frames": map[string]interface{}{
187 | "gps": gpsFrames,
188 | },
189 | }
190 | jsonValue, _ := json.Marshal(values)
191 | return jsonValue
192 | }
193 |
194 | func hookSend(outHook string, imei string, pkt *teltonika.Packet, logger *Logger) {
195 | jsonValue := buildJsonPacket(imei, pkt)
196 | if jsonValue == nil {
197 | return
198 | }
199 | res, err := http.Post(outHook, "application/json", bytes.NewBuffer(jsonValue))
200 | if err != nil {
201 | logger.Error.Printf("http post error (%v)", err)
202 | } else {
203 | logger.Info.Printf("packet sent to output hook, status: %s", res.Status)
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Teltonika codecs
2 |
3 | The `teltonika` package provides an implementation of
4 | [Teltonika](https://teltonika-gps.com/) tracker codecs,
5 | the library supports decoding regular messages from trackers
6 | and encoding/decoding commands and command responses
7 |
8 | Implemented Codec 8, 8E, 16, 12, 13, 14, 15 (tcp/udp) decode/encode
9 |
10 | The `ioelements` package can be used to represent IO Elements in human-readable
11 | format, see [tools](/tools)
12 |
13 | ### Example
14 |
15 | > more examples in [examples](/examples) folder (see [examples/README.md](/examples/README.md))
16 |
17 | ```go
18 | package main
19 |
20 | import (
21 | "encoding/hex"
22 | "encoding/json"
23 | "fmt"
24 |
25 | "github.com/alim-zanibekov/teltonika"
26 | "github.com/alim-zanibekov/teltonika/ioelements"
27 | )
28 |
29 | func main() {
30 | packetHex := "00000000000000A98E020000017357633410000F0DC39B2095964A00AC00F80B00000000000B000500F00100150400C8000" +
31 | "04501007156000500B5000500B600040018000000430FE00044011B000100F10000601B000000000000017357633BE1000F0DC39B209" +
32 | "5964A00AC00F80B000001810001000000000000000000010181002D11213102030405060708090A0B0C0D0E0F104545010ABC2121020" +
33 | "30405060708090A0B0C0D0E0F10020B010AAD020000BF30"
34 | packet, _ := hex.DecodeString(packetHex)
35 | _, decoded, err := teltonika.DecodeTCPFromSlice(packet)
36 | if err != nil {
37 | panic(err)
38 | }
39 | res, _ := json.Marshal(decoded)
40 | fmt.Printf("%s\n", res)
41 |
42 | decoder := ioelements.DefaultDecoder()
43 |
44 | elements := make(map[int][]*ioelements.IOElement)
45 | for i, data := range decoded.Packet.Data {
46 | elements[i] = make([]*ioelements.IOElement, len(data.Elements))
47 | for j, element := range data.Elements {
48 | elements[i][j], err = decoder.Decode("*", element.Id, element.Value)
49 | if err != nil {
50 | panic(err)
51 | }
52 | }
53 | }
54 |
55 | for i, items := range elements {
56 | fmt.Printf("Frame #%d\n", i)
57 | for _, element := range items {
58 | fmt.Printf(" %s\n", element)
59 | }
60 | }
61 | }
62 | ```
63 |
64 |
65 | Output (byte arrays in hex)
66 |
67 | ```json
68 | {
69 | "packet": {
70 | "codecId": 142,
71 | "data": [
72 | {
73 | "timestampMs": 1594898986000,
74 | "lng": 25.2560283,
75 | "lat": 54.667425,
76 | "altitude": 172,
77 | "angle": 248,
78 | "event_id": 0,
79 | "speed": 0,
80 | "satellites": 11,
81 | "priority": 0,
82 | "generationType": "Unknown",
83 | "elements": [
84 | {
85 | "id": 240,
86 | "value": "01"
87 | },
88 | {
89 | "id": 21,
90 | "value": "04"
91 | },
92 | {
93 | "id": 200,
94 | "value": "00"
95 | },
96 | {
97 | "id": 69,
98 | "value": "01"
99 | },
100 | {
101 | "id": 113,
102 | "value": "56"
103 | },
104 | {
105 | "id": 181,
106 | "value": "0005"
107 | },
108 | {
109 | "id": 182,
110 | "value": "0004"
111 | },
112 | {
113 | "id": 24,
114 | "value": "0000"
115 | },
116 | {
117 | "id": 67,
118 | "value": "0fe0"
119 | },
120 | {
121 | "id": 68,
122 | "value": "011b"
123 | },
124 | {
125 | "id": 241,
126 | "value": "0000601b"
127 | }
128 | ]
129 | },
130 | {
131 | "timestampMs": 1594898988001,
132 | "lng": 25.2560283,
133 | "lat": 54.667425,
134 | "altitude": 172,
135 | "angle": 248,
136 | "event_id": 385,
137 | "speed": 0,
138 | "satellites": 11,
139 | "priority": 0,
140 | "generationType": 255,
141 | "elements": [
142 | {
143 | "id": 385,
144 | "value": "11213102030405060708090a0b0c0d0e0f104545010abc212102030405060708090a0b0c0d0e0f10020b010aad"
145 | }
146 | ]
147 | }
148 | ]
149 | },
150 | "response": "00000002"
151 | }
152 | ```
153 |
154 | ```text
155 | Frame #0
156 | Movement: true
157 | GSM Signal: 4
158 | Sleep Mode: 0
159 | GNSS Status: 1
160 | Battery Level: 86%
161 | GNSS PDOP: 0.500
162 | GNSS HDOP: 0.400
163 | Speed: 0km/h
164 | Battery Voltage: 4.064V
165 | Battery Current: 0.283A
166 | Active GSM Operator: 24603
167 | Frame #1
168 | Beacon: 11213102030405060708090a0b0c0d0e0f104545010abc212102030405060708090a0b0c0d0e0f10020b010aad
169 | ```
170 |
171 |
172 |
173 | ### API
174 |
175 | Package `teltonika`
176 |
177 | Data structures:
178 |
179 | ```go
180 | package teltonika
181 |
182 | type PacketResponse []byte
183 |
184 | type DecodedUDP struct {
185 | PacketId uint16 // Packet ID
186 | AvlPacketId uint8 // AVL Packet ID
187 | Imei string // Device IMEI
188 | Packet *Packet // Decoded Packet
189 | Response PacketResponse // Response to received packet
190 | }
191 |
192 | type DecodedTCP struct {
193 | Packet *Packet // Decoded Packet
194 | Response PacketResponse // Response to received packet (4 bytes, len(Packet.Data))
195 | }
196 |
197 | type Packet struct {
198 | CodecID CodecId // Codec ID, if 8, 8E or 16 Data field is not nil, if 12, 13 or 14 Messages field is not nil
199 | Data []Data // Packet AVLData array
200 | Messages []Message // Packet Messages array (max 1 message)
201 | }
202 |
203 | type Data struct {
204 | TimestampMs uint64 // UNIX timestamp in milliseconds
205 | Lng float64 // Longitude, east – west position
206 | Lat float64 // Latitude, north – south position
207 | Altitude int16 // Meters above sea level
208 | Angle uint16 // Angle in degrees from the North Pole (clock-wise)
209 | EventID uint16 // If data is acquired on event this field contains IOElement id else 0
210 | Speed uint16 // Speed calculated from satellites (km/h)
211 | Satellites uint8 // Number of visible satellites
212 | Priority uint8 // Priority (0 Low, 1 High, 2 Panic)
213 | GenerationType GenerationType // Codec 16 generation type
214 | Elements []IOElement // Array containing IO Elements
215 | }
216 |
217 | type IOElementValue []byte
218 |
219 | type IOElement struct {
220 | Id uint16 // IO element ID
221 | Value IOElementValue // Value of the element (for codec 16 and 8 1-8 bytes, for codec 8E 1-X bytes)
222 | }
223 |
224 | type Message struct {
225 | Timestamp uint32 // UNIX timestamp in milliseconds (codecs 13,15)
226 | Type MessageType // Type (Command or Response)
227 | Imei string // Device IMEI (codecs 14,15)
228 | Text string // Command or Response represented as string
229 | }
230 |
231 | // DecodeConfig optional configuration that can be passed in all Decode* functions (last param).
232 | // By default, used - DecodeConfig { ioElementsAlloc: OnHeap }
233 | type DecodeConfig struct {
234 | ioElementsAlloc IOElementsAlloc // IOElement->Value allocation mode: `OnHeap` or `OnReadBuffer`
235 | }
236 |
237 | ```
238 |
239 | Methods:
240 |
241 | ```go
242 | package teltonika
243 |
244 | // DecodeTCPFromSlice
245 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) tcp packet from slice
246 | // returns the number of bytes read from 'inputBuffer' and decoded packet or an error
247 | func DecodeTCPFromSlice(inputBuffer []byte, config ...*DecodeConfig) (int, *DecodedTCP, error)
248 |
249 | // DecodeTCPFromReader
250 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) tcp packet from io.Reader
251 | // returns decoded packet or an error
252 | func DecodeTCPFromReader(input io.Reader, config ...*DecodeConfig) ([]byte, *DecodedTCP, error)
253 |
254 | // DecodeTCPFromReaderBuf
255 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) tcp packet from io.Reader
256 | // writes the read bytes to readBytes buffer (max packet size 1280 bytes)
257 | // returns the number of bytes read and decoded packet or an error
258 | func DecodeTCPFromReaderBuf(input io.Reader, readBytes []byte, config ...*DecodeConfig) (int, *DecodedTCP, error)
259 |
260 | // DecodeUDPFromSlice
261 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) udp packet from slice
262 | // returns the number of bytes read from 'inputBuffer' and decoded packet or an error
263 | func DecodeUDPFromSlice(inputBuffer []byte, config ...*DecodeConfig) (int, *DecodedUDP, error)
264 |
265 | // DecodeUDPFromReader
266 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) udp packet from io.Reader
267 | // returns the read buffer and decoded packet or an error
268 | func DecodeUDPFromReader(input io.Reader, config ...*DecodeConfig) ([]byte, *DecodedUDP, error)
269 |
270 | // DecodeUDPFromReaderBuf
271 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) udp packet from io.Reader
272 | // writes read bytes to readBytes slice (max packet size 1280 bytes)
273 | // returns the number of bytes read and decoded packet or an error
274 | func DecodeUDPFromReaderBuf(input io.Reader, readBytes []byte, config ...*DecodeConfig) (int, *DecodedUDP, error)
275 |
276 | // EncodePacketTCP
277 | // encode packet (12, 13, 14, 15, 8, 16, or 8 extended codec)
278 | // returns an array of bytes with encoded data or an error
279 | // note: implementations for 8, 16, 8E are practically not needed, they are made for testing
280 | func EncodePacketTCP(packet *Packet) ([]byte, error)
281 |
282 | // EncodePacketUDP
283 | // encode packet (12, 13, 14, 15, 8, 16, or 8 extended codec)
284 | // returns an array of bytes with encoded data or an error
285 | // note: all implementations are practically not needed, they are made for testing
286 | func EncodePacketUDP(imei string, packetId uint16, avlPacketId uint8, packet *Packet) ([]byte, error)
287 | ```
288 |
289 | Package `ioelements`
290 |
291 | Data structures:
292 |
293 | ```go
294 | package ioelements
295 |
296 | type IOElementDefinition struct {
297 | Id uint16 // I/O Element id
298 | Name string // Element name
299 | NumBytes int // Bytes count
300 | Type ElementType // Element type (Signed, Unsigned, HEX, ASCII)
301 | Min float64 // Min value if number
302 | Max float64 // Max value if number
303 | Multiplier float64 // Multiplier (used for numbers)
304 | Units string // Element units
305 | Description string // Element full description
306 | SupportedModels []string // List of device names that support this I/O element
307 | Groups []string // Element groups
308 | }
309 |
310 | type IOElement struct {
311 | Id uint16 // I/O Element id
312 | Value interface{} // Value (float64, int64, uint64, string)
313 | Definition *IOElementDefinition // I/O Element definition
314 | }
315 | ```
316 |
317 | Methods:
318 |
319 | ```go
320 | package ioelements
321 |
322 | // NewDecoder create new Decoder
323 | func NewDecoder(definitions []IOElementDefinition)
324 |
325 | // DefaultDecoder returns a decoder with I/O Element definitions represented in `ioelements_dump.go` file
326 | func DefaultDecoder()
327 |
328 | // GetElementInfo returns full description of I/O Element by its id and model name
329 | // If you don't know the model name, you can skip the model name check by passing '*' as the model name
330 | func (r *Decoder) GetElementInfo(modelName string, id uint16) (*IOElementDefinition, error)
331 |
332 | // Decode decodes an I/O Element by model name and id (result can be represented in numan-readable format)
333 | // If you don't know the model name, you can skip the model name check by passing '*' as the model name
334 | func (r *Decoder) Decode(modelName string, id uint16, buffer []byte) (*IOElement, error)
335 |
336 | // DecodeByDefinition decodes an I/O Element according to a given definition
337 | func (r *Decoder) DecodeByDefinition(def *IOElementDefinition, buffer []byte) (*IOElement, error)
338 | ```
339 |
340 | ### Simple benchmarks (go test -bench)
341 |
342 | Command:
343 |
344 | ```shell
345 | go test -bench=. -benchmem
346 | ```
347 |
348 | Output:
349 |
350 | ```text
351 | goos: darwin
352 | goarch: arm64
353 | pkg: github.com/alim-zanibekov/teltonika
354 | BenchmarkTCPDecode-10 2556680 472.8 ns/op 405 B/op 11 allocs/op
355 | BenchmarkTCPDecodeReader-10 2203923 546.8 ns/op 517 B/op 13 allocs/op
356 | BenchmarkUDPDecodeSlice-10 1582856 697.8 ns/op 1350 B/op 38 allocs/op
357 | BenchmarkUDPDecodeReader-10 1571578 779.7 ns/op 1550 B/op 40 allocs/op
358 | BenchmarkTCPDecodeAllocElementsOnReadBuffer-10 2882876 410.8 ns/op 382 B/op 5 allocs/op
359 | BenchmarkTCPDecodeReaderAllocElementsOnReadBuffer-10 2506252 478.1 ns/op 498 B/op 7 allocs/op
360 | BenchmarkUDPDecodeSliceAllocElementsOnReadBuffer-10 3202860 374.4 ns/op 1264 B/op 6 allocs/op
361 | BenchmarkUDPDecodeReaderAllocElementsOnReadBuffer-10 2589747 473.6 ns/op 1468 B/op 8 allocs/op
362 | BenchmarkEncodeTCP-10 16615262 75.58 ns/op 36 B/op 1 allocs/op
363 | BenchmarkEncodeUDP-10 22332055 53.40 ns/op 48 B/op 1 allocs/op
364 | BenchmarkCrc16IBMGenerateLookupTable-10 4424582 270.1 ns/op 512 B/op 1 allocs/op
365 | BenchmarkCrc16IBMWithLookupTable-10 455336 2641 ns/op 0 B/op 0 allocs/op
366 | BenchmarkCrc16IBMWithoutLookupTable-10 47612 25130 ns/op 0 B/op 0 allocs/op
367 | PASS
368 | ok github.com/alim-zanibekov/teltonika 22.685s
369 | ```
370 |
371 | As you can see from the results, passing the `&teltonika.DecodeConfig{teltonika.OnReadBuffer}`
372 | parameter to the Decode* function noticeably speeds them up, but this prevents the
373 | garbage collector from removing the byte array from which the packet
374 | was read until all references to it (Packet->Data->Elements->Value) are removed,
375 | so this option should be used if long-term packet storage in
376 | RAM is not required (almost always?)
377 |
--------------------------------------------------------------------------------
/examples/simple-tcp-server/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Alim Zanibekov
2 | //
3 | // Use of this source code is governed by an MIT-style
4 | // license that can be found in the LICENSE file or at
5 | // https://opensource.org/licenses/MIT.
6 |
7 | package main
8 |
9 | import (
10 | "bufio"
11 | "bytes"
12 | "encoding/binary"
13 | "encoding/hex"
14 | "encoding/json"
15 | "errors"
16 | "flag"
17 | "fmt"
18 | "log"
19 | "net"
20 | "net/http"
21 | "os"
22 | "strings"
23 | "sync"
24 | "time"
25 |
26 | "github.com/alim-zanibekov/teltonika"
27 | "github.com/alim-zanibekov/teltonika/ioelements"
28 | )
29 |
30 | var decodeConfig = &teltonika.DecodeConfig{IoElementsAlloc: teltonika.OnReadBuffer}
31 |
32 | type Logger struct {
33 | Info *log.Logger
34 | Error *log.Logger
35 | }
36 |
37 | type TrackersHub interface {
38 | SendPacket(imei string, packet *teltonika.Packet) error
39 | ListClients() []*Client
40 | }
41 |
42 | type TCPServer struct {
43 | address string
44 | clients *sync.Map
45 | logger *Logger
46 | readTimeout time.Duration
47 | writeTimeout time.Duration
48 | sLock sync.RWMutex
49 | OnPacket func(imei string, pkt *teltonika.Packet)
50 | OnClose func(imei string)
51 | OnConnect func(imei string)
52 | }
53 |
54 | type TCPClient struct {
55 | conn net.Conn
56 | connectedAt time.Time
57 | imei string
58 | }
59 |
60 | type Client struct {
61 | Imei string `json:"imei"`
62 | Addr string `json:"addr"`
63 | ConnectedAt time.Time `json:"connectedAt"`
64 | }
65 |
66 | //goland:noinspection GoUnusedExportedFunction
67 | func NewTCPServer(address string) *TCPServer {
68 | return &TCPServer{
69 | address: address, logger: &Logger{log.Default(), log.Default()}, clients: &sync.Map{},
70 | readTimeout: time.Minute * 5, writeTimeout: time.Minute * 5,
71 | }
72 | }
73 |
74 | func NewTCPServerLogger(address string, logger *Logger) *TCPServer {
75 | return &TCPServer{
76 | address: address, logger: logger, clients: &sync.Map{},
77 | readTimeout: time.Minute * 5, writeTimeout: time.Minute * 5,
78 | }
79 | }
80 |
81 | func (r *TCPServer) Run() error {
82 | logger := r.logger
83 |
84 | addr, err := net.ResolveTCPAddr("tcp", r.address)
85 | if err != nil {
86 | return fmt.Errorf("tcp address resolve error (%v)", err)
87 | }
88 |
89 | listener, err := net.ListenTCP("tcp", addr)
90 | if err != nil {
91 | return fmt.Errorf("tcp listener create error (%v)", err)
92 | }
93 |
94 | defer func() {
95 | _ = listener.Close()
96 | }()
97 |
98 | logger.Info.Println("tcp server listening at " + r.address)
99 |
100 | for {
101 | conn, err := listener.Accept()
102 | if err != nil {
103 | return fmt.Errorf("tcp connection accept error (%v)", err)
104 | }
105 | go r.handleConnection(conn)
106 | }
107 | }
108 |
109 | func (r *TCPServer) SendPacket(imei string, packet *teltonika.Packet) error {
110 | clientRaw, ok := r.clients.Load(imei)
111 | if !ok {
112 | return fmt.Errorf("client with imei '%s' not found", imei)
113 | }
114 | client := clientRaw.(*TCPClient)
115 |
116 | buf, err := teltonika.EncodePacketTCP(packet)
117 | if err != nil {
118 | return err
119 | }
120 |
121 | if _, err = client.conn.Write(buf); err != nil {
122 | return err
123 | }
124 |
125 | return nil
126 | }
127 |
128 | func (r *TCPServer) ListClients() []*Client {
129 | clients := make([]*Client, 0, 10)
130 | r.clients.Range(func(key, value interface{}) bool {
131 | client := value.(*TCPClient)
132 | clients = append(clients, &Client{client.imei, client.conn.RemoteAddr().String(), client.connectedAt})
133 | return true
134 | })
135 | return clients
136 | }
137 |
138 | func (r *TCPServer) handleConnection(conn net.Conn) {
139 | logger := r.logger
140 | client := &TCPClient{conn, time.Now(), ""}
141 | addr := conn.RemoteAddr().String()
142 | logKey := addr
143 | imei := ""
144 |
145 | updateReadDeadline := func() bool {
146 | if err := conn.SetReadDeadline(time.Now().Add(r.readTimeout)); err != nil {
147 | logger.Error.Printf("[%s]: SetReadDeadline error (%v)", logKey, err)
148 | return false
149 | }
150 | return true
151 | }
152 |
153 | writeWithDeadline := func(data []byte) bool {
154 | if err := conn.SetWriteDeadline(time.Now().Add(r.writeTimeout)); err != nil {
155 | logger.Error.Printf("[%s]: SetWriteDeadline error (%v)", logKey, err)
156 | return false
157 | }
158 | if _, err := conn.Write(data); err != nil {
159 | logger.Error.Printf("[%s]: error writing response (%v)", logKey, err)
160 | return false
161 | }
162 | return true
163 | }
164 |
165 | defer func() {
166 | if r.OnClose != nil && imei != "" {
167 | r.OnClose(imei)
168 | }
169 | logger.Info.Printf("[%s]: client disconnected", logKey)
170 | if imei != "" {
171 | r.clients.Delete(imei)
172 | }
173 | if err := conn.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
174 | logger.Error.Printf("[%s]: connection close error (%v)", logKey, err)
175 | }
176 | }()
177 |
178 | logger.Info.Printf("[%s]: connected", logKey)
179 | buf := make([]byte, 512)
180 | if !updateReadDeadline() {
181 | return
182 | }
183 | size, err := conn.Read(buf[:2])
184 | if err != nil {
185 | logger.Error.Printf("[%s]: connection read error (%v)", logKey, err)
186 | return
187 | }
188 | if size != 2 {
189 | logger.Error.Printf("[%s]: invalid imei message (read: %s)", logKey, hex.EncodeToString(buf[:2]))
190 | return
191 | }
192 | imeiLen := int(binary.BigEndian.Uint16(buf))
193 | if imeiLen > len(buf) {
194 | logger.Error.Printf("[%s]: imei length too big (read: %s)", logKey, hex.EncodeToString(buf[:2]))
195 | return
196 | }
197 | if !updateReadDeadline() {
198 | return
199 | }
200 | size, err = conn.Read(buf[:imeiLen])
201 | if err != nil {
202 | logger.Error.Printf("[%s]: connection read error (%v)", logKey, err)
203 | return
204 | }
205 | if size < imeiLen {
206 | logger.Error.Printf("[%s]: invalid read imei size (read: %s)", logKey, hex.EncodeToString(buf[:imeiLen]))
207 | return
208 | }
209 | imei = strings.TrimSpace(string(buf[:imeiLen]))
210 |
211 | if imei == "" {
212 | logger.Error.Printf("[%s]: invalid imei '%s'", logKey, imei)
213 | return
214 | }
215 |
216 | client.imei = imei
217 | logKey = fmt.Sprintf("%s-%s", imei, addr)
218 |
219 | if r.OnConnect != nil {
220 | r.OnConnect(imei)
221 | }
222 |
223 | logger.Info.Printf("[%s]: imei - %s", logKey, client.imei)
224 | storedClient, loaded := r.clients.LoadOrStore(imei, client)
225 | if loaded {
226 | otherClient := storedClient.(*TCPClient)
227 | otherAddr := otherClient.conn.RemoteAddr().String()
228 |
229 | logger.Info.Printf("[%s]: there is another client with the same imei (%s)", logKey, otherAddr)
230 | err = otherClient.conn.Close()
231 | if err != nil && !errors.Is(err, net.ErrClosed) {
232 | logger.Error.Printf("[%s]: other client (%s) connection close error (%v)", logKey, otherAddr, err)
233 | } else {
234 | logger.Info.Printf("[%s]: the connection with the other client was closed (%s)", logKey, otherAddr)
235 | }
236 |
237 | r.clients.Store(imei, client)
238 | }
239 |
240 | if !writeWithDeadline([]byte{1}) {
241 | return
242 | }
243 |
244 | readBuffer := make([]byte, 1300)
245 | reader := bufio.NewReader(conn)
246 | for {
247 | if !updateReadDeadline() {
248 | return
249 | }
250 |
251 | peek, err := reader.Peek(1)
252 | if err != nil {
253 | if !errors.Is(err, net.ErrClosed) {
254 | logger.Error.Printf("[%s]: read error (%v)", logKey, err)
255 | }
256 | return
257 | }
258 | if peek[0] == 0xFF { // ping packet
259 | if _, err = reader.Discard(1); err != nil {
260 | logger.Error.Printf("[%s]: reader discard error (%v)", logKey, err)
261 | return
262 | }
263 | logger.Info.Printf("[%s]: received ping", logKey)
264 | continue
265 | }
266 | read, res, err := teltonika.DecodeTCPFromReaderBuf(reader, readBuffer, decodeConfig)
267 | if err != nil {
268 | logger.Error.Printf("[%s]: packet decode error (%v)", logKey, err)
269 | return
270 | }
271 |
272 | if res.Response != nil && !writeWithDeadline(res.Response) {
273 | return
274 | }
275 |
276 | logger.Info.Printf("[%s]: message: %s", logKey, hex.EncodeToString(readBuffer[:read]))
277 | jsonData, err := json.Marshal(res.Packet)
278 | if err != nil {
279 | logger.Error.Printf("[%s]: marshaling error (%v)", logKey, err)
280 | } else {
281 | logger.Info.Printf("[%s]: decoded: %s", logKey, string(jsonData))
282 | for i, data := range res.Packet.Data {
283 | elements := make([]string, len(data.Elements))
284 | for j, element := range data.Elements {
285 | it, err := ioelements.DefaultDecoder().Decode("*", element.Id, element.Value)
286 | if err != nil {
287 | break
288 | }
289 | elements[j] = it.String()
290 | }
291 | logger.Info.Printf("[%s]: io elements [frame #%d]: %s", logKey, i, strings.Join(elements, ", "))
292 | }
293 | }
294 |
295 | if r.OnPacket != nil {
296 | r.OnPacket(imei, res.Packet)
297 | }
298 | }
299 | }
300 |
301 | type HTTPServer struct {
302 | address string
303 | hub TrackersHub
304 | respChan *sync.Map
305 | logger *Logger
306 | }
307 |
308 | //goland:noinspection GoUnusedExportedFunction
309 | func NewHTTPServer(address string, hub TrackersHub) *HTTPServer {
310 | return &HTTPServer{address: address, respChan: &sync.Map{}, hub: hub}
311 | }
312 |
313 | func NewHTTPServerLogger(address string, hub TrackersHub, logger *Logger) *HTTPServer {
314 | return &HTTPServer{address: address, respChan: &sync.Map{}, hub: hub, logger: logger}
315 | }
316 |
317 | func (hs *HTTPServer) Run() error {
318 | logger := hs.logger
319 |
320 | handler := http.NewServeMux()
321 |
322 | handler.HandleFunc("/cmd", hs.handleCmd)
323 |
324 | handler.HandleFunc("/list-clients", hs.listClients)
325 |
326 | logger.Info.Println("http server listening at " + hs.address)
327 |
328 | err := http.ListenAndServe(hs.address, handler)
329 | if err != nil {
330 | return fmt.Errorf("http listen error (%v)", err)
331 | }
332 | return nil
333 | }
334 |
335 | func (hs *HTTPServer) WriteMessage(imei string, message *teltonika.Message) {
336 | ch, ok := hs.respChan.Load(imei)
337 | if ok {
338 | select {
339 | case ch.(chan *teltonika.Message) <- message:
340 | }
341 | }
342 | }
343 |
344 | func (hs *HTTPServer) ClientDisconnected(imei string) {
345 | ch, ok := hs.respChan.Load(imei)
346 | if ok {
347 | select {
348 | case ch.(chan *teltonika.Message) <- nil:
349 | }
350 | }
351 | }
352 |
353 | func (hs *HTTPServer) listClients(w http.ResponseWriter, _ *http.Request) {
354 | body, _ := json.Marshal(hs.hub.ListClients())
355 | w.Header().Set("Content-Type", "application/json")
356 | _, err := w.Write(body)
357 | if err != nil {
358 | hs.logger.Error.Printf("http write error (%v)", err)
359 | return
360 | }
361 | }
362 |
363 | func (hs *HTTPServer) handleCmd(w http.ResponseWriter, r *http.Request) {
364 | logger := hs.logger
365 |
366 | params := r.URL.Query()
367 | imei := params.Get("imei")
368 | buf := make([]byte, 512)
369 | n, _ := r.Body.Read(buf)
370 | cmd := string(buf[:n])
371 |
372 | packet := &teltonika.Packet{
373 | CodecID: teltonika.Codec12,
374 | Data: nil,
375 | Messages: []teltonika.Message{{Type: teltonika.TypeCommand, Text: strings.TrimSpace(cmd)}},
376 | }
377 |
378 | result := make(chan *teltonika.Message, 1)
379 | defer close(result)
380 | for {
381 | if _, loaded := hs.respChan.LoadOrStore(imei, result); !loaded {
382 | break
383 | }
384 | time.Sleep(time.Millisecond * 100)
385 | }
386 |
387 | defer hs.respChan.Delete(imei)
388 |
389 | w.Header().Set("Content-Type", "application/json")
390 | if err := hs.hub.SendPacket(imei, packet); err != nil {
391 | logger.Error.Printf("send packet error (%v)", err)
392 | w.WriteHeader(http.StatusBadRequest)
393 | body, _ := json.Marshal(map[string]interface{}{"error": err.Error()})
394 | _, err = w.Write(body)
395 | if err != nil {
396 | logger.Error.Printf("http write error (%v)", err)
397 | }
398 | } else {
399 | logger.Info.Printf("command '%s' sent to '%s'", cmd, imei)
400 | ticker := time.NewTimer(time.Minute * 3)
401 | defer ticker.Stop()
402 |
403 | select {
404 | case msg := <-result:
405 | if msg != nil {
406 | body, _ := json.Marshal(map[string]interface{}{"response": msg.Text})
407 | _, err = w.Write(body)
408 | } else {
409 | w.WriteHeader(http.StatusServiceUnavailable)
410 | _, err = w.Write([]byte(`{"error":"tracker disconnected"}`))
411 | }
412 | case <-ticker.C:
413 | w.WriteHeader(http.StatusGatewayTimeout)
414 | _, err = w.Write([]byte(`{"error":"tracker response timeout exceeded"}`))
415 | }
416 |
417 | if err != nil {
418 | logger.Error.Printf("http write error (%v)", err)
419 | }
420 | }
421 | }
422 |
423 | func main() {
424 | var httpAddress string
425 | var tcpAddress string
426 | var outHook string
427 | var readTimeout time.Duration
428 | var writeTimeout time.Duration
429 | flag.StringVar(&tcpAddress, "address", "0.0.0.0:8080", "tcp server address")
430 | flag.StringVar(&httpAddress, "http", "0.0.0.0:8081", "http server address")
431 | flag.StringVar(&outHook, "hook", "", "output hook\nfor example: http://localhost:8080/push")
432 | flag.DurationVar(&readTimeout, "read-timeout", time.Minute*2, "receive timeout")
433 | flag.DurationVar(&writeTimeout, "write-timeout", time.Minute*2, "send timeout")
434 | flag.Parse()
435 |
436 | logger := &Logger{
437 | Info: log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime),
438 | Error: log.New(os.Stdout, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile),
439 | }
440 |
441 | serverTcp := NewTCPServerLogger(tcpAddress, logger)
442 | serverHttp := NewHTTPServerLogger(httpAddress, serverTcp, logger)
443 |
444 | serverTcp.writeTimeout = writeTimeout
445 | serverTcp.readTimeout = readTimeout
446 | serverTcp.OnPacket = func(imei string, pkt *teltonika.Packet) {
447 | if pkt.Messages != nil && len(pkt.Messages) > 0 {
448 | serverHttp.WriteMessage(imei, &pkt.Messages[0])
449 | }
450 | if pkt.Data != nil && outHook != "" {
451 | go hookSend(outHook, imei, pkt, logger)
452 | }
453 | }
454 |
455 | serverTcp.OnClose = func(imei string) {
456 | serverHttp.ClientDisconnected(imei)
457 | }
458 |
459 | go func() {
460 | panic(serverTcp.Run())
461 | }()
462 | panic(serverHttp.Run())
463 | }
464 |
465 | func buildJsonPacket(imei string, pkt *teltonika.Packet) []byte {
466 | if pkt.Data == nil {
467 | return nil
468 | }
469 | gpsFrames := make([]interface{}, 0)
470 | for _, frame := range pkt.Data {
471 | gpsFrames = append(gpsFrames, map[string]interface{}{
472 | "timestamp": int64(frame.TimestampMs / 1000.0),
473 | "lat": frame.Lat,
474 | "lon": frame.Lng,
475 | })
476 | }
477 | if len(gpsFrames) == 0 {
478 | return nil
479 | }
480 | values := map[string]interface{}{
481 | "deveui": imei,
482 | "time": time.Now().String(),
483 | "frames": map[string]interface{}{
484 | "gps": gpsFrames,
485 | },
486 | }
487 | jsonValue, _ := json.Marshal(values)
488 | return jsonValue
489 | }
490 |
491 | func hookSend(outHook string, imei string, pkt *teltonika.Packet, logger *Logger) {
492 | jsonValue := buildJsonPacket(imei, pkt)
493 | if jsonValue == nil {
494 | return
495 | }
496 | res, err := http.Post(outHook, "application/json", bytes.NewBuffer(jsonValue))
497 | if err != nil {
498 | logger.Error.Printf("http post error (%v)", err)
499 | } else {
500 | logger.Info.Printf("packet sent to output hook, status: %s", res.Status)
501 | }
502 | }
503 |
--------------------------------------------------------------------------------
/tools/io_elements_gen.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/gob"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io"
10 | "log"
11 | "net/http"
12 | "net/url"
13 | "os"
14 | "regexp"
15 | "sort"
16 | "strconv"
17 | "strings"
18 |
19 | "github.com/PuerkitoBio/goquery"
20 | "github.com/gocarina/gocsv"
21 | "github.com/google/go-cmp/cmp"
22 | "github.com/jessevdk/go-flags"
23 | "golang.org/x/exp/slices"
24 | )
25 |
26 | type ElementType uint8
27 | type Preference uint8
28 | type StringSlice []string
29 |
30 | const (
31 | PreferFirst Preference = iota
32 | PreferSecond
33 | PreferNone
34 | )
35 |
36 | const (
37 | IOElementSigned ElementType = iota
38 | IOElementUnsigned
39 | IOElementHEX
40 | IOElementASCII
41 | )
42 |
43 | type IOElementDefinition struct {
44 | Id uint16 `json:"id" csv:"Id"`
45 | Name string `json:"name" csv:"Name"`
46 | NumBytes int `json:"numBytes" csv:"NumBytes"`
47 | Type ElementType `json:"type" csv:"Type"`
48 | Min float64 `json:"min" csv:"Min"`
49 | Max float64 `json:"max" csv:"Max"`
50 | Multiplier float64 `json:"multiplier" csv:"Multiplier"`
51 | Units string `json:"units" csv:"Units"`
52 | Description string `json:"description" csv:"Description"`
53 | SupportedModels StringSlice `json:"supportedModels" csv:"SupportedModels"`
54 | Groups StringSlice `json:"groups" csv:"Groups"`
55 | }
56 |
57 | type NetworkParserOptions struct {
58 | Models []string `short:"m" long:"model" description:"models to parse, can be specified multiple times" default:"FMB920" default:"FMC650" required:"yes"`
59 | CsvOutput flags.Filename `long:"csv-out" description:"csv output file path"`
60 | Cache flags.Filename `long:"cache" description:"path to http cache file" default:"./cache.bin"`
61 | NoCache bool `long:"no-cache" description:"disable http cache"`
62 | NoFollow bool `long:"no-follow" description:"disable recursive links following"`
63 | UrlPattern string `long:"url-pattern" description:"url generation pattern from specified model names\n substring '{model}' will be substituted\n" default:"https://wiki.teltonika-gps.com/view/{model}_Teltonika_Data_Sending_Parameters_ID"`
64 | }
65 |
66 | type CsvParserOptions struct {
67 | Arg struct {
68 | InputFile flags.Filename `positional-arg-name:"InputCsvFile"`
69 | } `positional-args:"yes" required:"yes"`
70 | }
71 |
72 | type Options struct {
73 | ParseNetwork NetworkParserOptions `command:"load-net" optional:"true"`
74 | ParseCsv CsvParserOptions `command:"load-csv" optional:"true"`
75 | GenOutput flags.Filename `short:"o" long:"gen-out" default:"./ioelements_dump.go" description:"output file path for I/O elements definitions list"`
76 | NoGen bool `long:"no-gen" description:"disable go file generation"`
77 | GenPkgName string `long:"gen-pkg-name" default:"main" description:"package name for generated file"`
78 | GenInternal bool `long:"gen-internal" description:"generate file for internal usage in ioelements package"`
79 | }
80 |
81 | var options Options
82 | var parser = flags.NewParser(&options, flags.Default)
83 |
84 | func main() {
85 | log.SetFlags(0)
86 | if _, err := parser.Parse(); err != nil {
87 | var flagsErr *flags.Error
88 | if errors.As(err, &flagsErr) && errors.Is(flagsErr.Type, flags.ErrHelp) {
89 | os.Exit(0)
90 | }
91 | os.Exit(1)
92 | }
93 | switch parser.Command.Active.Name {
94 | case "load-net":
95 | res := collectDefinitionsNetwork(options.ParseNetwork.Models)
96 | if options.ParseNetwork.CsvOutput != "" {
97 | dumpCsv(res, string(options.ParseNetwork.CsvOutput))
98 | }
99 | if !options.NoGen {
100 | generate(res, string(options.GenOutput))
101 | }
102 | case "load-csv":
103 | res := readCsv(string(options.ParseCsv.Arg.InputFile))
104 | if !options.NoGen {
105 | generate(res, string(options.GenOutput))
106 | }
107 | }
108 | }
109 |
110 | func (r *StringSlice) MarshalCSV() (string, error) {
111 | return strings.Join(*r, ", "), nil
112 | }
113 |
114 | func (r *StringSlice) UnmarshalCSV(csv string) (err error) {
115 | res := strings.Split(csv, ",")
116 | for i := range res {
117 | res[i] = strings.TrimSpace(res[i])
118 | }
119 | *r = res
120 | return nil
121 | }
122 |
123 | func (r *ElementType) MarshalCSV() (string, error) {
124 | switch *r {
125 | case IOElementASCII:
126 | return "ASCII", nil
127 | case IOElementSigned:
128 | return "Signed", nil
129 | case IOElementUnsigned:
130 | return "Unsigned", nil
131 | case IOElementHEX:
132 | return "Hex", nil
133 | }
134 | return "", fmt.Errorf("unknown element type: %v", *r)
135 | }
136 |
137 | func (r *ElementType) UnmarshalCSV(csv string) (err error) {
138 | switch csv {
139 | case "ASCII":
140 | *r = IOElementASCII
141 | case "Signed":
142 | *r = IOElementSigned
143 | case "Unsigned":
144 | *r = IOElementUnsigned
145 | case "Hex":
146 | *r = IOElementHEX
147 | default:
148 | return fmt.Errorf("unknown element type: %v", csv)
149 | }
150 | return nil
151 | }
152 |
153 | func generate(data []*IOElementDefinition, output string) {
154 | allSupportedModelsMap := map[string]bool{}
155 | allSupportedModels := make([]string, 0)
156 | for _, it := range data {
157 | for _, model := range it.SupportedModels {
158 | if !allSupportedModelsMap[model] {
159 | allSupportedModels = append(allSupportedModels, model)
160 | }
161 | allSupportedModelsMap[model] = true
162 | }
163 | }
164 | slices.Sort(allSupportedModels)
165 |
166 | wrapStr := func(s interface{}) string {
167 | str, _ := json.Marshal(s)
168 | return string(str)
169 | }
170 |
171 | var sb strings.Builder
172 | sb.WriteString("package " + options.GenPkgName + "\n\n")
173 | prefix := ""
174 | if !options.GenInternal {
175 | sb.WriteString(`import "github.com/alim-zanibekov/teltonika/ioelements"` + "\n\n")
176 | prefix = "ioelements."
177 | }
178 |
179 | sb.WriteString("var supportedModels = map[string]bool{")
180 | k := 0
181 |
182 | for _, model := range allSupportedModels {
183 | if k == 0 {
184 | sb.WriteString(wrapStr(model) + ": true")
185 | } else {
186 | sb.WriteString(", " + wrapStr(model) + ": true")
187 | }
188 | k++
189 | }
190 | sb.WriteString("}\n\n")
191 | sb.WriteString("var ioElementDefinitions = []" + prefix + "IOElementDefinition{\n")
192 | var typesMap = map[ElementType]string{
193 | IOElementUnsigned: "IOElementUnsigned",
194 | IOElementSigned: "IOElementSigned",
195 | IOElementHEX: "IOElementHEX",
196 | IOElementASCII: "IOElementASCII",
197 | }
198 | for _, m := range data {
199 | supportedModels := make([]string, 0)
200 | for _, it := range m.SupportedModels {
201 | if it != "" {
202 | supportedModels = append(supportedModels, wrapStr(it))
203 | }
204 | }
205 | groups := make([]string, 0)
206 | for _, it := range m.Groups {
207 | if it != "" {
208 | groups = append(groups, wrapStr(it))
209 | }
210 | }
211 |
212 | line := fmt.Sprintf("\t{%d, %s, %d, %s, %s, %s, %s, %s, %s, []string{%s}, []string{%s}},\n",
213 | m.Id, wrapStr(m.Name), m.NumBytes, prefix+typesMap[m.Type],
214 | strconv.FormatFloat(m.Min, 'f', -1, 64),
215 | strconv.FormatFloat(m.Max, 'f', -1, 64),
216 | strconv.FormatFloat(m.Multiplier, 'f', -1, 64),
217 | wrapStr(m.Units), wrapStr(m.Description),
218 | strings.Join(supportedModels, ", "), strings.Join(groups, ", "),
219 | )
220 |
221 | sb.WriteString(line)
222 | }
223 | sb.WriteString("}\n")
224 |
225 | f, err := os.Create(output)
226 | if err != nil {
227 | log.Fatal(err)
228 | }
229 | defer func() { _ = f.Close() }()
230 |
231 | _, err = f.WriteString(sb.String())
232 | if err != nil {
233 | log.Fatalf("error writing to %s: %v", options.GenOutput, err)
234 | }
235 | }
236 |
237 | func dumpCsv(data []*IOElementDefinition, path string) {
238 | f, err := os.Create(path)
239 | if err != nil {
240 | panic(err)
241 | }
242 | defer func() { _ = f.Close() }()
243 | err = gocsv.Marshal(data, f)
244 | if err != nil {
245 | log.Fatalf("error marshalling csv: %v", err)
246 | }
247 | }
248 |
249 | func readCsv(path string) []*IOElementDefinition {
250 | f, err := os.Open(path)
251 | if err != nil {
252 | panic(err)
253 | }
254 | defer func() { _ = f.Close() }()
255 | var res []*IOElementDefinition
256 | err = gocsv.Unmarshal(f, &res)
257 | if err != nil {
258 | log.Fatalf("error marshalling csv: %v", err)
259 | }
260 | return res
261 | }
262 |
263 | func collectDefinitionsNetwork(modelNames []string) []*IOElementDefinition {
264 | result := make([]*IOElementDefinition, 0)
265 |
266 | mergeSupportedModels := func(a *IOElementDefinition, b *IOElementDefinition) {
267 | res := unique(append(a.SupportedModels, b.SupportedModels...))
268 | sort.Strings(res)
269 | a.SupportedModels = res
270 | b.SupportedModels = res
271 | }
272 |
273 | loadPagesRecursively(modelNames, func(current *IOElementDefinition) {
274 | toReplace := make([]int, 0)
275 | for i, definition := range result {
276 | if definition.Id != current.Id {
277 | continue
278 | }
279 | if isDefinitionsEqual(definition, current) {
280 | return
281 | }
282 |
283 | if isDefinitionsEqualExceptSupportedModels(definition, current) {
284 | mergeSupportedModels(definition, current)
285 | return
286 | }
287 |
288 | if !overlap(definition.SupportedModels, current.SupportedModels) {
289 | continue
290 | }
291 |
292 | if containsAll(definition.SupportedModels, current.SupportedModels) {
293 | return
294 | }
295 |
296 | if containsAll(current.SupportedModels, definition.SupportedModels) {
297 | toReplace = append(toReplace, i)
298 | continue
299 | }
300 |
301 | almostEqual, preference := isDefinitionsEqualExceptMinMaxMultiplier(definition, current)
302 | if almostEqual && preference != PreferNone {
303 | if preference == PreferSecond {
304 | toReplace = append(toReplace, i)
305 | mergeSupportedModels(definition, current)
306 | continue
307 | } else {
308 | mergeSupportedModels(definition, current)
309 | return
310 | }
311 | }
312 |
313 | almostEqual, preference = preferOneThatHasMultiplierAndMaxAndMinAndDescriptionAndUnits(definition, current)
314 | if almostEqual && preference != PreferNone {
315 | if preference == PreferSecond {
316 | toReplace = append(toReplace, i)
317 | mergeSupportedModels(definition, current)
318 | continue
319 | } else {
320 | mergeSupportedModels(definition, current)
321 | return
322 | }
323 | }
324 |
325 | // normalization
326 | // 32512 - https://wiki.teltonika-gps.com/view/FMB150_Teltonika_Data_Sending_Parameters_ID
327 | // 16128 - https://wiki.teltonika-gps.com/view/FMB920_Teltonika_Data_Sending_Parameters_ID
328 | if current.Id == 90 && current.Max == 32512 && definition.Max == 16128 {
329 | toReplace = append(toReplace, i)
330 | continue
331 | }
332 | if definition.Id == 90 && definition.Max == 32512 && current.Max == 16128 {
333 | return
334 | }
335 |
336 | log.Printf("\n1. %s\n2. %s\n%v", ignoreError(json.Marshal(definition)), ignoreError(json.Marshal(current)), almostEqual)
337 | return
338 | }
339 |
340 | if len(toReplace) > 0 {
341 | for _, i := range toReplace {
342 | result = append(result[:i], result[i+1:]...)
343 | }
344 | }
345 | result = append(result, current)
346 | })
347 |
348 | slices.SortStableFunc(result, func(a, b *IOElementDefinition) int {
349 | return int(a.Id) - int(b.Id)
350 | })
351 |
352 | return result
353 | }
354 |
355 | func isDefinitionsEqualExceptSupportedModels(a *IOElementDefinition, b *IOElementDefinition) bool {
356 | scalarFieldsEqual := a.Type == b.Type &&
357 | a.NumBytes == b.NumBytes &&
358 | a.Multiplier == b.Multiplier &&
359 | a.Min == b.Min &&
360 | a.Max == b.Max &&
361 | a.Units == b.Units
362 |
363 | return scalarFieldsEqual
364 | }
365 |
366 | func isDefinitionsEqual(a *IOElementDefinition, b *IOElementDefinition) bool {
367 | return isDefinitionsEqualExceptSupportedModels(a, b) && cmp.Equal(a.SupportedModels, b.SupportedModels)
368 | }
369 |
370 | func preferOneThatHasMultiplierAndMaxAndMinAndDescriptionAndUnits(
371 | a *IOElementDefinition,
372 | b *IOElementDefinition,
373 | ) (bool, Preference) {
374 | mainFieldsEqual := a.Type == b.Type &&
375 | a.NumBytes == b.NumBytes
376 |
377 | if !mainFieldsEqual {
378 | return false, PreferNone
379 | }
380 |
381 | if a.Max != 0 && a.Units != "" && a.Description != "" &&
382 | b.Max == 0 && b.Units == "" && b.Description == "" {
383 | return true, PreferFirst
384 | }
385 |
386 | if b.Max != 0 && b.Units != "" && b.Description != "" &&
387 | a.Max == 0 && a.Units == "" && a.Description == "" {
388 | return true, PreferSecond
389 | }
390 |
391 | return false, PreferNone
392 | }
393 |
394 | func isDefinitionsEqualExceptMinMaxMultiplier(
395 | a *IOElementDefinition,
396 | b *IOElementDefinition,
397 | ) (bool, Preference) {
398 | scalarFieldsEqual := a.Type == b.Type &&
399 | a.NumBytes == b.NumBytes &&
400 | a.Units == b.Units
401 |
402 | if scalarFieldsEqual && cmp.Equal(a.SupportedModels, b.SupportedModels) {
403 | aCost := 0
404 | bCost := 0
405 | if a.Multiplier != b.Multiplier {
406 | if a.Multiplier != 1.0 && b.Multiplier != 1.0 {
407 | return true, PreferNone
408 | }
409 | if a.Multiplier != 1.0 {
410 | aCost++
411 | } else {
412 | bCost++
413 | }
414 | }
415 |
416 | if a.Min != b.Min {
417 | if a.Min != 0.0 && b.Min != 0.0 {
418 | return true, PreferNone
419 | }
420 | if a.Min != 0.0 {
421 | aCost++
422 | } else {
423 | bCost++
424 | }
425 | }
426 |
427 | if a.Max != b.Max {
428 | if a.Min != 0.0 && b.Max != 0.0 {
429 | return true, PreferNone
430 | }
431 | if a.Max != 0.0 {
432 | aCost++
433 | } else {
434 | bCost++
435 | }
436 | }
437 |
438 | if aCost == bCost {
439 | return true, PreferNone
440 | }
441 | if aCost > bCost {
442 | return true, PreferFirst
443 | }
444 | return true, PreferSecond
445 | }
446 |
447 | return false, PreferNone
448 | }
449 |
450 | func modelNameToUrl(modelName string) string {
451 | res := strings.ReplaceAll(options.ParseNetwork.UrlPattern, "{model}", modelName)
452 |
453 | if modelName == "TMT250" || modelName == "GH5200" || modelName == "TFT100" || modelName == "TST100" ||
454 | modelName == "TAT100" || modelName == "TAT140" || modelName == "TAT141" || modelName == "TAT240" {
455 | res = strings.ReplaceAll(res, "_Teltonika_Data_Sending_Parameters_ID", "_AVL_ID_List")
456 | }
457 |
458 | return res
459 | }
460 |
461 | // Load pages recursively by parsing "HW Support" column and adding links to another models to the stack
462 | func loadPagesRecursively(modelNames []string, onDefinition func(it *IOElementDefinition)) {
463 | urlsToProcess := make([]string, 0)
464 | for _, it := range modelNames {
465 | urlsToProcess = append(urlsToProcess, modelNameToUrl(it))
466 | }
467 |
468 | for i := 0; i < len(urlsToProcess); i++ {
469 | log.Printf("Parsing %s, %d pages remaining", urlsToProcess[i], len(urlsToProcess)-1-i)
470 |
471 | loadPage(urlsToProcess[i], func(page string) {
472 | modelName := ""
473 | processPageTable(page, func(document *goquery.Document, tds *goquery.Selection) {
474 | if !options.ParseNetwork.NoFollow {
475 | otherPages := tds.Eq(9).Find("a").Map(func(_ int, it *goquery.Selection) string {
476 | link := it.AttrOr("href", "")
477 | if link == "" {
478 | return ""
479 | }
480 | pUrl, err := url.Parse(link)
481 | if err != nil {
482 | log.Printf("Error parsing link %s: %v", link, err)
483 | return ""
484 | }
485 | title := pUrl.Query().Get("title")
486 | if pUrl.Path == "/index.php" {
487 | if title != "" {
488 | return modelNameToUrl(title)
489 | }
490 | return ""
491 | }
492 | p := strings.Split(pUrl.Path, "/")
493 | return modelNameToUrl(p[len(p)-1])
494 | })
495 |
496 | for _, link := range otherPages {
497 | if link != "" && !slices.Contains(urlsToProcess, link) {
498 | urlsToProcess = append(urlsToProcess, link)
499 | }
500 | }
501 | }
502 |
503 | if modelName == "" {
504 | modelName = strings.Split(document.Find("#firstHeading").First().Text(), " ")[0]
505 | }
506 |
507 | items := parseTableRow(modelName, tds)
508 | if items == nil {
509 | return
510 | }
511 |
512 | for _, item := range items {
513 | onDefinition(item)
514 | }
515 | })
516 | })
517 | }
518 | }
519 |
520 | func loadPage(url string, onPage func(data string)) {
521 | var decodedMap = make(map[string][]byte)
522 | if !options.ParseNetwork.NoCache {
523 | if _, err := os.Stat(string(options.ParseNetwork.Cache)); !errors.Is(err, os.ErrNotExist) {
524 | f, err := os.Open(string(options.ParseNetwork.Cache))
525 | if err != nil {
526 | log.Fatal("Failed to open cache file", err)
527 | }
528 | if err = gob.NewDecoder(f).Decode(&decodedMap); err != nil {
529 | log.Fatal("Failed to decode cache map", err)
530 | }
531 | }
532 | }
533 | if decodedMap[url] == nil {
534 | res, err := http.Get(url)
535 | if err != nil {
536 | log.Printf("Failed to connect to the target page %v", err)
537 | return
538 | }
539 |
540 | defer func() { _ = res.Body.Close() }()
541 |
542 | if res.StatusCode != 200 {
543 | log.Printf("HTTP Error %d: %s", res.StatusCode, res.Status)
544 | decodedMap[url] = []byte("")
545 | } else {
546 | var data []byte
547 | if data, err = io.ReadAll(res.Body); err != nil {
548 | log.Printf("Failed to read response body %v", err)
549 | return
550 | }
551 | decodedMap[url] = data
552 | }
553 | if !options.ParseNetwork.NoCache {
554 | f, err := os.Create(string(options.ParseNetwork.Cache))
555 | defer func() { _ = f.Close() }()
556 |
557 | if err = gob.NewEncoder(f).Encode(decodedMap); err != nil {
558 | log.Fatal("Failed to encode cache map", err)
559 | }
560 | }
561 | }
562 | onPage(string(decodedMap[url]))
563 | }
564 |
565 | func processPageTable(pageContents string, onRow func(document *goquery.Document, tds *goquery.Selection)) {
566 | document, err := goquery.NewDocumentFromReader(bytes.NewBufferString(pageContents))
567 | if err != nil {
568 | log.Printf("Failed to parse the HTML document %v", err)
569 | }
570 |
571 | tables := document.Find(".mw-parser-output").First().Find("table")
572 |
573 | tables.Each(func(_ int, it *goquery.Selection) {
574 | it.Find("tr").Each(func(_ int, tr *goquery.Selection) {
575 | cols := tr.Find("td")
576 | if cols.Size() == 0 {
577 | return
578 | }
579 | onRow(document, cols)
580 | })
581 | })
582 | }
583 |
584 | var rowTypesMap = map[string]ElementType{
585 | "unsigned": IOElementUnsigned,
586 | "signed": IOElementSigned,
587 | "hex": IOElementHEX,
588 | "ascii": IOElementASCII,
589 | "unsigned long int": IOElementUnsigned,
590 | "unsiged": IOElementUnsigned,
591 | "": IOElementASCII,
592 | }
593 |
594 | func parseTableRow(modelName string, tds *goquery.Selection) []*IOElementDefinition {
595 | c := tds.Map(func(_ int, it *goquery.Selection) string { return asText(it) })
596 |
597 | rawId, rawName, rawNumBytes, rawType, rawMin, rawMax, rawMultiplier, rawUnits, rawDescription :=
598 | c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8]
599 |
600 | var numBytes int
601 | var min, max, multiplier float64
602 | var elementType ElementType
603 |
604 | if rawNumBytes == "" && rawType == "" {
605 | numBytes = -1
606 | } else if _, err := fmt.Sscan(rawNumBytes, &numBytes); err != nil {
607 | numBytes = -1
608 | if !strings.Contains(strings.ToLower(rawNumBytes), "variable") {
609 | log.Printf("[%s] invalid num bytes - %s", rawId, rawNumBytes)
610 | }
611 | }
612 |
613 | var ok bool
614 | if elementType, ok = rowTypesMap[strings.ToLower(rawType)]; !ok {
615 | log.Printf("[%s] invalid type '%s', set to unsigned, numBytes set to -1", rawId, rawType)
616 | elementType = IOElementUnsigned
617 | numBytes = -1
618 | }
619 |
620 | if rawMin != "" && rawMax != "" && !strings.HasPrefix(rawMax, "0x") && !strings.HasPrefix(rawMin, "0x") {
621 | if _, err := fmt.Sscan(rawMin, &min); err != nil {
622 | log.Printf("[%s] Unable to parse string (min) '%s' as float64, skipping", rawId, rawMin)
623 | return nil
624 | }
625 | if _, err := fmt.Sscan(rawMax, &max); err != nil {
626 | log.Printf("[%s] Unable to parse string (max) '%s' as float64, skipping", rawId, rawMax)
627 | return nil
628 | }
629 | }
630 | multiplier = 1
631 | if rawMultiplier != "" {
632 | if rawMultiplier == "acc and braking: 0.01" {
633 | multiplier = 1.0 // we need another io element to make decision, just skip
634 | } else if _, err := fmt.Sscan(rawMultiplier, &multiplier); err != nil {
635 | log.Printf("[%s] Unable to parse string (multiplier) '%s' as float64", rawId, rawMultiplier)
636 | return nil
637 | }
638 | }
639 |
640 | supportedModels := make([]string, 1)
641 | supportedModels[0] = modelName
642 | tds.Eq(9).Find("a").Each(func(_ int, it *goquery.Selection) {
643 | if asText(it) != "" {
644 | supportedModels = append(supportedModels, asText(it))
645 | }
646 | })
647 | supportedModels = unique(supportedModels)
648 |
649 | groups := make([]string, 0)
650 | if len(c) >= 11 {
651 | for _, it := range strings.Split(c[10], ",") {
652 | groups = append(groups, strings.Trim(it, " \n\t"))
653 | }
654 | }
655 | groups = unique(groups)
656 |
657 | if len(groups) == 0 {
658 | log.Printf("[%s] No groups found, keeping", rawId)
659 | }
660 |
661 | if elementType != IOElementHEX && elementType != IOElementASCII &&
662 | (numBytes == -1 || numBytes > 8) {
663 | elementType = IOElementHEX
664 | }
665 |
666 | name := rawName
667 | units := rawUnits
668 | description := rawDescription
669 |
670 | slices.Sort(supportedModels)
671 | slices.Sort(groups)
672 |
673 | def := &IOElementDefinition{
674 | Id: 0,
675 | Name: name,
676 | NumBytes: numBytes,
677 | Type: elementType,
678 | Min: min,
679 | Max: max,
680 | Multiplier: multiplier,
681 | Units: units,
682 | Description: description,
683 | SupportedModels: supportedModels,
684 | Groups: groups,
685 | }
686 |
687 | if strings.HasPrefix(rawId, "COM1") {
688 | ids := strings.Split(strings.ReplaceAll(rawId, "COM1", ""), "COM2")
689 | id1Str := strings.Trim(strings.ReplaceAll(ids[0], "-", ""), " \n\t")
690 | id2Str := strings.Trim(strings.ReplaceAll(ids[1], "-", ""), " \n\t")
691 | id1, err := strconv.Atoi(id1Str)
692 | if err != nil {
693 | log.Printf("[%s] unable to parse id '%s'", rawId, id1Str)
694 | }
695 | id2, err := strconv.Atoi(id2Str)
696 | if err != nil {
697 | log.Printf("[%s] unable to parse id '%s'", rawId, id2Str)
698 | }
699 | r1 := *def
700 | r1.Id = uint16(id1)
701 | r1.Name = "COM1 " + r1.Name
702 | r2 := *def
703 | r2.Id = uint16(id2)
704 | r2.Name = "COM2 " + r2.Name
705 |
706 | normalize(&r1)
707 | normalize(&r2)
708 |
709 | return []*IOElementDefinition{&r1, &r2}
710 | } else {
711 | id, err := strconv.Atoi(rawId)
712 | if err != nil {
713 | log.Printf("[%s] unable to parse id '%s'", rawId, rawId)
714 | }
715 | def.Id = uint16(id)
716 |
717 | normalize(def)
718 | return []*IOElementDefinition{def}
719 | }
720 | }
721 |
722 | func normalize(it *IOElementDefinition) {
723 | if (it.Id == 9 || it.Id == 6) && it.Units == "mV" {
724 | it.Units = "V"
725 | }
726 | if it.Id == 89 && it.Max == 65535 {
727 | it.Max = 100
728 | }
729 | if it.Id == 30 && it.Type == IOElementASCII {
730 | it.Type = IOElementUnsigned
731 | // I am not sure about this patch
732 | // uint8 https://wiki.teltonika-gps.com/view/FMB920_Teltonika_Data_Sending_Parameters_ID
733 | // ASCII https://wiki.teltonika-gps.com/view/FMM250_Teltonika_Data_Sending_Parameters_ID
734 | }
735 | if it.Id == 168 && it.Max == 6553 {
736 | it.Max = 65535
737 | }
738 | if it.Id == 103 && it.Max == 1677215 {
739 | it.Max = 16777215
740 | }
741 | if (it.Id == 278 || it.Id == 275 || it.Id == 272 || it.Id == 269) && it.NumBytes == 2 {
742 | it.NumBytes = 1
743 | }
744 | if sliceAny([]int{
745 | 478, 477, 476, 475, 474, 473, 472, 471, 470, 469, 468, 467, 466, 465, 464, 463,
746 | }, func(i int) bool { return i == int(it.Id) }) && it.NumBytes == 8 {
747 | it.NumBytes = 4
748 | }
749 | if sliceAny([]int{
750 | 10812, 10813, 10814, 10815,
751 | }, func(i int) bool { return i == int(it.Id) }) && it.Max == 1 {
752 | it.Max = 0
753 | }
754 | if sliceAny([]int{
755 | 10824, 10825, 10826, 10827, 10836, 10837, 10838, 10839,
756 | }, func(i int) bool { return i == int(it.Id) }) && it.Max == 65535 {
757 | it.Max = 0
758 | }
759 | if sliceAny([]int{
760 | 10839, 10838, 10837, 10836, 10839, 10838, 10837, 10836,
761 | }, func(i int) bool { return i == int(it.Id) }) && it.Max == 0 {
762 | it.Max = 32767
763 | }
764 | if sliceAny([]int{
765 | 463, 467, 471, 475,
766 | }, func(i int) bool { return i == int(it.Id) }) && it.Max == 4294967295 && it.NumBytes == 256 {
767 | it.Max = 0
768 | it.NumBytes = 2
769 | }
770 | if it.Units == "-ml" {
771 | it.Units = "ml"
772 | }
773 | if it.Units == "enum (bit field)" || it.Units == "String" {
774 | it.Units = ""
775 | }
776 | if strings.ToLower(it.Units) == "minutes" {
777 | it.Units = "min."
778 | }
779 | }
780 |
781 | // Utils
782 |
783 | func asText(it *goquery.Selection) string {
784 | str := strings.Trim(it.Text(), " \n\t")
785 | if str == "-" || str == "–" {
786 | return ""
787 | }
788 | ws := regexp.MustCompile(`[ \t ]+`)
789 | res := strings.Trim(ws.ReplaceAllString(str, " "), " \n\t")
790 |
791 | if len(res) > 2 && strings.Count(res, `"`) == 2 && string(res[0]) == `"` && string(res[len(res)-1]) == `"` {
792 | return res[1 : len(res)-2]
793 | }
794 | return res
795 | }
796 |
797 | func ignoreError[T any](val T, _ error) T {
798 | return val
799 | }
800 |
801 | func sliceAll[T any](val []T, check func(T) bool) bool {
802 | for _, v := range val {
803 | if !check(v) {
804 | return false
805 | }
806 | }
807 | return true
808 | }
809 |
810 | func sliceAny[T any](val []T, check func(T) bool) bool {
811 | for _, v := range val {
812 | if check(v) {
813 | return true
814 | }
815 | }
816 | return false
817 | }
818 |
819 | func overlap[T comparable](a1 []T, a2 []T) bool {
820 | return sliceAny(a1, func(t T) bool { return slices.Contains(a2, t) }) ||
821 | sliceAny(a2, func(t T) bool { return slices.Contains(a1, t) })
822 | }
823 |
824 | func containsAll[T comparable](a1 []T, a2 []T) bool {
825 | return sliceAll(a2, func(t T) bool { return slices.Contains(a1, t) })
826 | }
827 |
828 | func unique[T comparable](inputSlice []T) []T {
829 | uniqueSlice := make([]T, 0, len(inputSlice))
830 | seen := make(map[T]bool, len(inputSlice))
831 | for _, element := range inputSlice {
832 | if !seen[element] {
833 | uniqueSlice = append(uniqueSlice, element)
834 | seen[element] = true
835 | }
836 | }
837 | return uniqueSlice
838 | }
839 |
--------------------------------------------------------------------------------
/teltonika_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Alim Zanibekov
2 | //
3 | // Use of this source code is governed by an MIT-style
4 | // license that can be found in the LICENSE file or at
5 | // https://opensource.org/licenses/MIT.
6 |
7 | package teltonika
8 |
9 | import (
10 | "bytes"
11 | "encoding/hex"
12 | "encoding/json"
13 | "io"
14 | "math/rand"
15 | "strings"
16 | "testing"
17 | "time"
18 | )
19 |
20 | func TestCodec12Encode(t *testing.T) {
21 | buffer, err := EncodePacketTCP(&Packet{
22 | CodecID: Codec12,
23 | Data: nil,
24 | Messages: []Message{{Type: TypeCommand, Text: "getinfo"}},
25 | })
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 |
30 | encoded := hex.EncodeToString(buffer)
31 | expected := strings.ToLower("000000000000000F0C010500000007676574696E666F0100004312")
32 |
33 | if encoded != expected {
34 | t.Error("encoded: ", encoded)
35 | t.Error("expected:", expected)
36 | }
37 | }
38 |
39 | func TestCodec13Encode(t *testing.T) {
40 | buffer, err := EncodePacketTCP(&Packet{
41 | CodecID: Codec13,
42 | Data: nil,
43 | Messages: []Message{{Type: TypeResponse, Text: "getinfo", Timestamp: 176276256}},
44 | })
45 | if err != nil {
46 | t.Fatal(err)
47 | }
48 |
49 | encoded := hex.EncodeToString(buffer)
50 | expected := strings.ToLower("00000000000000130d01060000000b0a81c320676574696e666f0100001d6b")
51 |
52 | if encoded != expected {
53 | t.Error("encoded: ", encoded)
54 | t.Error("expected:", expected)
55 | }
56 | }
57 |
58 | func TestCodec14Encode(t *testing.T) {
59 | buffer, err := EncodePacketTCP(&Packet{
60 | CodecID: Codec14,
61 | Data: nil,
62 | Messages: []Message{{Type: TypeCommand, Text: "getver", Imei: "352093081452251"}},
63 | })
64 | if err != nil {
65 | t.Fatal(err)
66 | }
67 |
68 | encoded := hex.EncodeToString(buffer)
69 | expected := strings.ToLower("00000000000000160E01050000000E0352093081452251676574766572010000D2C1")
70 |
71 | if encoded != expected {
72 | t.Error("encoded: ", encoded)
73 | t.Error("expected:", expected)
74 | }
75 | }
76 |
77 | func TestCodec15Encode(t *testing.T) {
78 | buffer, err := EncodePacketTCP(&Packet{
79 | CodecID: Codec15,
80 | Data: nil,
81 | Messages: []Message{{Type: 0x0B, Text: "Hello!\n", Imei: "123456789123456", Timestamp: 1699440036}},
82 | })
83 | if err != nil {
84 | t.Fatal(err)
85 | }
86 |
87 | encoded := hex.EncodeToString(buffer)
88 | expected := strings.ToLower("000000000000001b0f010b00000013654b65a4012345678912345648656c6c6f210a01000093d6")
89 |
90 | if encoded != expected {
91 | t.Error("encoded: ", encoded)
92 | t.Error("expected:", expected)
93 | }
94 | }
95 |
96 | func TestCodecs8EncodeTCP(t *testing.T) {
97 | buffer, err := EncodePacketTCP(&Packet{
98 | CodecID: Codec8,
99 | Data: []Data{{
100 | TimestampMs: 1720003694000,
101 | Lat: 42.373737,
102 | Lng: 42.373737,
103 | Altitude: 731,
104 | Angle: 262,
105 | EventID: 239,
106 | Speed: 0,
107 | Satellites: 5,
108 | Priority: 1,
109 | GenerationType: Unknown,
110 | Elements: []IOElement{
111 | {Id: 239, Value: []byte{1}},
112 | {Id: 240, Value: []byte{1}},
113 | {Id: 1, Value: []byte{0}},
114 | {Id: 66, Value: []byte{0x31, 0x4a}},
115 | {Id: 67, Value: []byte{0x10, 0x31}},
116 | {Id: 9, Value: []byte{0, 0x2b}},
117 | },
118 | }},
119 | })
120 |
121 | if err != nil {
122 | t.Fatal(err)
123 | }
124 |
125 | encoded := hex.EncodeToString(buffer)
126 | expected := strings.ToLower("000000000000003008010000019078358db0011941b81a1941b81a02db0106050000ef0603ef01f00101000342314a43103109002b0000010000c897")
127 |
128 | if encoded != expected {
129 | t.Error("encoded: ", encoded)
130 | t.Error("expected:", expected)
131 | }
132 | }
133 |
134 | func TestCodecs8EEncodeTCP(t *testing.T) {
135 | msg11, _ := hex.DecodeString("000000003544c87a")
136 | msg14, _ := hex.DecodeString("000000001dd7e06a")
137 | buffer, err := EncodePacketTCP(&Packet{
138 | CodecID: Codec8E,
139 | Data: []Data{{
140 | TimestampMs: 1560166592000,
141 | Lat: 0, Lng: 0, Altitude: 0, Angle: 0, EventID: 1, Speed: 0, Satellites: 0, Priority: 1,
142 | GenerationType: Unknown,
143 | Elements: []IOElement{
144 | {Id: 1, Value: []byte{1}},
145 | {Id: 17, Value: []byte{0, 0x1d}},
146 | {Id: 16, Value: []byte{0x01, 0x5e, 0x2c, 0x88}},
147 | {Id: 11, Value: msg11},
148 | {Id: 14, Value: msg14},
149 | },
150 | }},
151 | })
152 |
153 | if err != nil {
154 | t.Fatal(err)
155 | }
156 |
157 | encoded := hex.EncodeToString(buffer)
158 | expected := strings.ToLower("000000000000004A8E010000016B412CEE000100000000000000000000000000000000010005000100010100010011001D00010010015E2C880002000B000000003544C87A000E000000001DD7E06A00000100002994")
159 |
160 | if encoded != expected {
161 | t.Error("encoded: ", encoded)
162 | t.Error("expected:", expected)
163 | }
164 | }
165 |
166 | func TestCodecs16EncodeTCP(t *testing.T) {
167 | buffer, err := EncodePacketTCP(&Packet{
168 | CodecID: Codec16,
169 | Data: []Data{{
170 | TimestampMs: 1562760414000,
171 | Lat: 0, Lng: 0, Altitude: 0, Angle: 0, EventID: 11, Speed: 0, Satellites: 0, Priority: 0,
172 | GenerationType: OnChange,
173 | Elements: []IOElement{
174 | {Id: 1, Value: []byte{0}},
175 | {Id: 3, Value: []byte{0}},
176 | {Id: 11, Value: []byte{0, 0x27}},
177 | {Id: 66, Value: []byte{0x56, 0x3a}},
178 | },
179 | }, {
180 | TimestampMs: 1562760415000,
181 | Lat: 0, Lng: 0, Altitude: 0, Angle: 0, EventID: 11, Speed: 0, Satellites: 0, Priority: 0,
182 | GenerationType: OnChange,
183 | Elements: []IOElement{
184 | {Id: 1, Value: []byte{0}},
185 | {Id: 3, Value: []byte{0}},
186 | {Id: 11, Value: []byte{0, 0x26}},
187 | {Id: 66, Value: []byte{0x56, 0x3a}},
188 | },
189 | }},
190 | })
191 |
192 | if err != nil {
193 | t.Fatal(err)
194 | }
195 |
196 | encoded := hex.EncodeToString(buffer)
197 | expected := strings.ToLower("000000000000005F10020000016BDBC7833000000000000000000000000000000000000B05040200010000030002000B00270042563A00000000016BDBC7871800000000000000000000000000000000000B05040200010000030002000B00260042563A00000200005FB3")
198 |
199 | if encoded != expected {
200 | t.Error("encoded: ", encoded)
201 | t.Error("expected:", expected)
202 | }
203 | }
204 |
205 | func TestCodecs8EncodeUDP(t *testing.T) {
206 | buffer, err := EncodePacketUDP("352093086403655", 51966, 5, &Packet{
207 | CodecID: Codec8,
208 | Data: []Data{{
209 | TimestampMs: 1560407006000,
210 | Lat: 0, Lng: 0, Altitude: 0, Angle: 0, EventID: 1, Speed: 0, Satellites: 0, Priority: 1,
211 | GenerationType: Unknown,
212 | Elements: []IOElement{
213 | {Id: 21, Value: []byte{3}},
214 | {Id: 1, Value: []byte{1}},
215 | {Id: 66, Value: []byte{0x5d, 0xbc}},
216 | },
217 | }},
218 | })
219 |
220 | if err != nil {
221 | t.Fatal(err)
222 | }
223 |
224 | encoded := hex.EncodeToString(buffer)
225 | expected := strings.ToLower("003DCAFE0105000F33353230393330383634303336353508010000016B4F815B30010000000000000000000000000000000103021503010101425DBC000001")
226 |
227 | if encoded != expected {
228 | t.Error("encoded: ", encoded)
229 | t.Error("expected:", expected)
230 | }
231 | }
232 |
233 | func TestCodecs8EEncodeUDP(t *testing.T) {
234 | msg16, _ := hex.DecodeString("015e2c88")
235 | msg11, _ := hex.DecodeString("000000003544c87a")
236 | msg14, _ := hex.DecodeString("000000001dd7e06a")
237 | buffer, err := EncodePacketUDP("352093086403655", 51966, 7, &Packet{
238 | CodecID: Codec8E,
239 | Data: []Data{{
240 | TimestampMs: 1560407121000,
241 | Lat: 0, Lng: 0, Altitude: 0, Angle: 0, EventID: 1, Speed: 0, Satellites: 0, Priority: 1,
242 | GenerationType: Unknown,
243 | Elements: []IOElement{
244 | {Id: 1, Value: []byte{1}},
245 | {Id: 17, Value: []byte{0, 0x9d}},
246 | {Id: 16, Value: msg16},
247 | {Id: 11, Value: msg11},
248 | {Id: 14, Value: msg14},
249 | },
250 | }},
251 | })
252 |
253 | if err != nil {
254 | t.Fatal(err)
255 | }
256 |
257 | encoded := hex.EncodeToString(buffer)
258 | expected := strings.ToLower("005FCAFE0107000F3335323039333038363430333635358E010000016B4F831C680100000000000000000000000000000000010005000100010100010011009D00010010015E2C880002000B000000003544C87A000E000000001DD7E06A000001")
259 |
260 | if encoded != expected {
261 | t.Error("encoded: ", encoded)
262 | t.Error("expected:", expected)
263 | }
264 | }
265 |
266 | func TestCodecs16EncodeUDP(t *testing.T) {
267 | buffer, err := EncodePacketUDP("352094085231592", 51966, 7, &Packet{
268 | CodecID: Codec16,
269 | Data: []Data{{
270 | TimestampMs: 1447804801000,
271 | Lat: 0, Lng: 0, Altitude: 0, Angle: 0, EventID: 239, Speed: 0, Satellites: 0, Priority: 0,
272 | GenerationType: OnChange,
273 | Elements: []IOElement{
274 | {Id: 1, Value: []byte{0}},
275 | {Id: 3, Value: []byte{0}},
276 | {Id: 180, Value: []byte{0}},
277 | {Id: 239, Value: []byte{1}},
278 | {Id: 66, Value: []byte{0x11, 0x1a}},
279 | },
280 | }},
281 | })
282 |
283 | if err != nil {
284 | t.Fatal(err)
285 | }
286 |
287 | encoded := hex.EncodeToString(buffer)
288 | expected := strings.ToLower("0048CAFE0107000F33353230393430383532333135393210010000015117E40FE80000000000000000000000000000000000EF05050400010000030000B40000EF01010042111A000001")
289 |
290 | if encoded != expected {
291 | t.Error("encoded: ", encoded)
292 | t.Error("expected:", expected)
293 | }
294 | }
295 |
296 | func TestTCPCodecsDecodeWithoutErrors(t *testing.T) {
297 | cases := []string{
298 | "000000000000003608010000016B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000010000C7CF",
299 | "000000000000002808010000016B40D9AD80010000000000000000000000000000000103021503010101425E100000010000F22A",
300 | "000000000000004308020000016B40D57B480100000000000000000000000000000001010101000000000000016B40D5C198010000000000000000000000000000000101010101000000020000252C",
301 | "000000000000005F10020000016BDBC7833000000000000000000000000000000000000B05040200010000030002000B00270042563A00000000016BDBC7871800000000000000000000000000000000000B05040200010000030002000B00260042563A00000200005FB3",
302 | "000000000000004A8E010000016B412CEE000100000000000000000000000000000000010005000100010100010011001D00010010015E2C880002000B000000003544C87A000E000000001DD7E06A00000100002994",
303 | "00000000000000A98E020000017357633410000F0DC39B2095964A00AC00F80B00000000000B000500F00100150400C800004501007156000500B5000500B600040018000000430FE00044011B000100F10000601B000000000000017357633BE1000F0DC39B2095964A00AC00F80B000001810001000000000000000000010181002D11213102030405060708090A0B0C0D0E0F104545010ABC212102030405060708090A0B0C0D0E0F10020B010AAD020000BF30",
304 | "000000000000000F0C010500000007676574696E666F0100004312",
305 | "00000000000000130d01060000000b0a81c320676574696e666f0100001d6b",
306 | "00000000000000160E01050000000E0352093081452251676574766572010000D2C1",
307 | "000000000000001b0f010b00000013654b65a4012345678912345648656c6c6f210a01000093d6",
308 | "00000000000003c68e04000001909d6417c801062c583a25ce28b9004f004530000000ef0011000500ef0000f00100150300c800004501000a00b5000800b60004004234a90018000000430ef700440052001100900012fc4c0013fb77000f03e8000200f100005e89001000003f9a00000000000001909d606e5201062bc70c25ccd6d50096010032000000ef0011000500ef0100f00100150500c800004501000a00b5000800b60005004237780018000000430ed500440000001100da0012fd0e0013f9ef000f03e8000200f100005e89001000003a6400000000000001909d4f2c5001062bc70c25ccd6d5009601002c000000ef0011000500ef0000f00100150500c800004501000a00b5000800b60005004234b10018000000430f0300440041001100db0012fd0d0013f9e9000f0047000200f100005e89001000003a6400000000000001909d4e03d501062c0f5025cdd1170068008130004800f700030002013d0100f70500000000000000010101025801dffe02f95d01c2fdc4f97001b1fde2f96f0193fdd2f97001a1fe01f95f0171fdfff95001b1fdf3f97e01a1fe01f95f0200fdd5f97d01eefe12f95c01affe31f96d01d0fe33f9aa01cffe62f9a901b1fe43f9ba01a0fe51f99b0200fe05f99b01d0fe33f99b0170fe6ff99b0161fe6ff9ab0164fe22f9cd0192fe22f99d0183fe32f9cc0170fe6ff99b0142fe4ff99d0133fe6ef9bc0172fe30f99d01c2fe15f9cb019ffe70f99a0182fe11f97e0183fdf2f99f0152fe2ff98e0163fe21f9ae0181fe2ff96e0152fe2ff98e0162fe30f98e0191fe11f97e01c0fe12f97d019ffe20f94e0180fe2ff95e0192fde1f96001a1fdf1f95f01d1fdc4f96f01a2fdc2f95101bffdf0f9300182fde1f96101d1fdb3f94101a1fdd0f93201c2fda3f95101b1fdd1f94101b1fdd1f94101effde3f94e01b0fdf1f94001c0fde1f94001c1fdd2f9500171fdeef92201b0fde1f94001a1fdc1f93201a0fdc0f9130171fdcff92301dffdb2f9120190fdfff9300191fde0f9410191fe00f95f0172fdd0f94201cffde1f92001bdfdeef8e201affdd0f91201c0fdc2f93101a1fdb1f93301bffde0f92101f1fd74f92301a1fd80f8f601d1fd83f91401a0fdaff8f401a0fde0f92101affdc0f90301bffdd0f90201dffdb1f90201cefdbff8e301eefda2f8f3019ffdaef8c501f0fd94f932019ffddff90201b0fe01f94f01ddfdfff90001bbfe4cf8ee01befe30f94d01cdfe1ff90f01cffdf2f94f019ffe1ff93f01b1fdd2f9600191fe00f95f0184fdc3f9910192fe12f99d01a0fe10f95e0180fe4ff97c01a1fe31f98c01a0fe21f96e0193fdf2f98f0191fe21f98d0400006a96",
309 | }
310 |
311 | for _, s := range cases {
312 | buf, _ := hex.DecodeString(s)
313 | size, _, err := DecodeTCPFromSlice(buf)
314 | if err != nil {
315 | t.Fatal(err)
316 | }
317 | if size != len(buf) {
318 | t.Error("[DecodeTCPFromSlice] payload not fully processed")
319 | }
320 | }
321 |
322 | for _, s := range cases {
323 | buf, _ := hex.DecodeString(s)
324 | reader := bytes.NewReader(buf)
325 | read, _, err := DecodeTCPFromReader(reader)
326 | if err != nil {
327 | t.Fatal(err)
328 | }
329 | if reader.Len() != 0 {
330 | t.Error("[DecodeTCPFromReader] payload not fully processed")
331 | }
332 | if hex.Dump(read) != hex.Dump(buf) {
333 | t.Error("[DecodeTCPFromReader] read bytes buffer invalid")
334 | }
335 | }
336 |
337 | for _, s := range cases {
338 | buf, _ := hex.DecodeString(s)
339 | reader := bytes.NewReader(buf)
340 | output := make([]byte, 1300)
341 | n, _, err := DecodeTCPFromReaderBuf(reader, output)
342 | if err != nil {
343 | t.Fatal(err)
344 | }
345 | if reader.Len() != 0 {
346 | t.Error("[DecodeTCPFromReaderBuf] payload not fully processed")
347 | }
348 | if hex.Dump(output[:n]) != hex.Dump(buf) {
349 | t.Error("[DecodeTCPFromReaderBuf] read bytes buffer invalid")
350 | }
351 | }
352 | }
353 |
354 | func TestTCPCodecsDecodeMustFail(t *testing.T) {
355 | cases := []string{
356 | "000001000000003608010000016B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000010000C7C1",
357 | "000000000000003608010000016B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000050000C7C1",
358 | "000000000000003608010000016B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000010000C7C1",
359 | "000000000000002808010000016B40D9AD80010000000000000000000000000000000103021503010101425E100000010000F23A",
360 | "000000000000003604010000001B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000050000C7C1",
361 | "000000000000003611010000019B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000050000C7C1",
362 | "000000000000004308020000016B40D57B480100000000000000000000000000000001010101000000000000016B40D5C198010000000000000000000000000000000101010101000000020000254C",
363 | "000000000000005F10020000016BDBC7833000000000000000000000000000000000000B05040200010000030002000B00270042563A00000000016BDBC7871800000000000000000000000000000000000B05040200010000030002000B00260042563A00000200005F13",
364 | "000000000000004A8E010000016B412CEE000100000000000000000000000000000000010005000100010100010011001D00010010015E2C880002000B000000003544C87A000E000000001DD7E06A00000100002991",
365 | "00000000000000A98E020000017357633410000F0DC39B2095964A00AC00F80B00000000000B000500F00100150400C800004501007156000500B5000500B600040018000000430FE00044011B000100F10000601B000000000000017357633BE1000F0DC39B2095964A00AC00F80B000001810001000000000000000000010181002D11213102030405060708090A0B0C0D0E0F104545010ABC212102030405060708090A0B0C0D0E0F10020B010AAD020000BF40",
366 | }
367 |
368 | for _, s := range cases {
369 | buf, _ := hex.DecodeString(s)
370 | _, _, err := DecodeTCPFromSlice(buf)
371 | if err == nil {
372 | t.Error("[DecodeTCPFromSlice] invalid payload processed successfully")
373 | }
374 | }
375 | }
376 |
377 | func TestUDPCodecsDecodeWithoutErrors(t *testing.T) {
378 | cases := []string{
379 | "005FCAFE0107000F3335323039333038363430333635358E010000016B4F831C680100000000000000000000000000000000010005000100010100010011009D00010010015E2C880002000B000000003544C87A000E000000001DD7E06A000001",
380 | "003DCAFE0105000F33353230393330383634303336353508010000016B4F815B30010000000000000000000000000000000103021503010101425DBC000001",
381 | "0086CAFE0101000F3335323039333038353639383230368E0100000167EFA919800200000000000000000000000000000000FC0013000800EF0000F00000150500C80000450200010000710000FC00000900B5000000B600000042305600CD432A00CE6064001100090012FF22001303D1000F0000000200F1000059D90010000000000000000001",
382 | "0083CAFE0101000F3335323039333038353639383230368E0100000167F1AEEC00000A750E8F1D43443100F800B210000000000012000700EF0000F00000150500C800004501000100007142000900B5000600B6000500422FB300CD432A00CE60640011000700120007001303EC000F0000000200F1000059D90010000000000000000001",
383 | "01E4CAFE0126000F333532303934303839333937343634080400000163C803B420010A259E1A1D4A057D00DA0128130057421B0A4503F00150051503EF01510052005900BE00C1000AB50008B60005427025CD79D8CE605A5400005500007300005A0000C0000007C700000018F1000059D910002D32C85300000000570000000064000000F7BF000000000000000163C803AC50010A25A9D21D4A01B600DB0128130056421B0A4503F00150051503EF01510052005900BE00C1000AB50008B6000542702ECD79D8CE605A5400005500007300005A0000C0000007C700000017F1000059D910002D32B05300000000570000000064000000F7BF000000000000000163C803A868010A25B5581D49FE5400DB0127130057421B0A4503F00150051503EF01510052005900BE00C1000AB50008B60005427039CD79D8CE605A5400005500007300005A0000C0000007C700000017F1000059D910002D32995300000000570000000064000000F7BF000000000000000163C803A4B2010A25CC861D49F75C00DB0124130058421B0A4503F00150051503EF01510052005900BE00C1000AB50008B6000542703CCD79D8CE605A5400005500007300005A0000C0000007C700000018F1000059D910002D32695300000000570000000064000000F7BF000000000004",
384 | }
385 |
386 | for _, s := range cases {
387 | buf, _ := hex.DecodeString(s)
388 | size, _, err := DecodeUDPFromSlice(buf)
389 | if err != nil {
390 | t.Fatal(err)
391 | }
392 | if size != len(buf) {
393 | t.Error("[DecodeUDPFromSlice] payload not fully processed")
394 | }
395 | }
396 |
397 | for _, s := range cases {
398 | buf, _ := hex.DecodeString(s)
399 | reader := bytes.NewReader(buf)
400 | read, _, err := DecodeUDPFromReader(reader)
401 | if err != nil {
402 | t.Fatal(err)
403 | }
404 | if reader.Len() != 0 {
405 | t.Error("[DecodeUDPFromReader] payload not fully processed")
406 | }
407 | if hex.Dump(read) != hex.Dump(buf) {
408 | t.Error("[DecodeUDPFromReader] read bytes buffer invalid")
409 | }
410 | }
411 |
412 | for _, s := range cases {
413 | buf, _ := hex.DecodeString(s)
414 | reader := bytes.NewReader(buf)
415 | output := make([]byte, 1300)
416 | n, _, err := DecodeUDPFromReaderBuf(reader, output)
417 | if err != nil {
418 | t.Fatal(err)
419 | }
420 | if reader.Len() != 0 {
421 | t.Error("[DecodeUDPFromReaderBuf] payload not fully processed")
422 | }
423 | if hex.Dump(output[:n]) != hex.Dump(buf) {
424 | t.Error("[DecodeUDPFromReaderBuf] read bytes buffer invalid")
425 | }
426 | }
427 | }
428 |
429 | func TestUDPCodecsDecodeMustFail(t *testing.T) {
430 | cases := []string{
431 | "005FCAFE010700043335323039333038363430333635358E010000016B4F831C680100000000000000000000000000000000010005000100010100010011009D00010010015E2C880002000B000000003544C87A000E000000001DD7E06A000001",
432 | "013DCAFE0105000F33353230393330383634303336353508010000016B4F815B30010000000000000000000000000000000103021503010101425DBC000001",
433 | "015BCAFE0101000F33353230393430383532333135393210070000015117E40FE80000000000000000000000000000000000EF05050400010000030000B40000EF01010042111A000001",
434 | }
435 |
436 | for _, s := range cases {
437 | buf, _ := hex.DecodeString(s)
438 | _, _, err := DecodeUDPFromSlice(buf)
439 | if err == nil {
440 | t.Error("[DecodeUDPFromSlice] invalid payload processed successfully")
441 | }
442 | }
443 | }
444 |
445 | func TestCodecsDecodeCommandResponseWithoutErrors(t *testing.T) {
446 | cases := []string{
447 | "00000000000000900C010600000088494E493A323031392F372F323220373A3232205254433A323031392F372F323220373A3533205253543A32204552523A312053523A302042523A302043463A302046473A3020464C3A302054553A302F302055543A3020534D533A30204E4F4750533A303A3330204750533A31205341543A302052533A332052463A36352053463A31204D443A30010000C78F",
448 | "00000000000000370C01060000002F4449313A31204449323A30204449333A302041494E313A302041494E323A313639323420444F313A3020444F323A3101000066E3",
449 | "00000000000000AB0E0106000000A303520930814522515665723A30332E31382E31345F3034204750533A41584E5F352E31305F333333332048773A464D42313230204D6F643A313520494D45493A33353230393330383134353232353120496E69743A323031382D31312D323220373A313320557074696D653A3137323334204D41433A363042444430303136323631205350433A312830292041584C3A30204F42443A3020424C3A312E362042543A340100007AAE",
450 | }
451 |
452 | for _, s := range cases {
453 | buf, _ := hex.DecodeString(s)
454 | size, _, err := DecodeTCPFromSlice(buf)
455 | if err != nil {
456 | t.Fatal(err)
457 | }
458 | if size != len(buf) {
459 | t.Error("[DecodeTCPFromSlice] payload not fully processed")
460 | }
461 | }
462 |
463 | for _, s := range cases {
464 | buf, _ := hex.DecodeString(s)
465 | reader := bytes.NewReader(buf)
466 | read, _, err := DecodeTCPFromReader(reader)
467 | if err != nil {
468 | t.Fatal(err)
469 | }
470 | if reader.Len() != 0 {
471 | t.Error("[DecodeTCPFromReader] payload not fully processed")
472 | }
473 | if hex.Dump(read) != hex.Dump(buf) {
474 | t.Error("[DecodeTCPFromReader] read bytes buffer invalid")
475 | }
476 | }
477 | }
478 |
479 | func TestPacketJSONMarshalUnmarshal(t *testing.T) {
480 | cases := []string{
481 | "00000000000000900C010600000088494E493A323031392F372F323220373A3232205254433A323031392F372F323220373A3533205253543A32204552523A312053523A302042523A302043463A302046473A3020464C3A302054553A302F302055543A3020534D533A30204E4F4750533A303A3330204750533A31205341543A302052533A332052463A36352053463A31204D443A30010000C78F",
482 | "00000000000000370C01060000002F4449313A31204449323A30204449333A302041494E313A302041494E323A313639323420444F313A3020444F323A3101000066E3",
483 | "000000000000005F10020000016BDBC7833000000000000000000000000000000000000B05040200010000030002000B00270042563A00000000016BDBC7871800000000000000000000000000000000000B05040200010000030002000B00260042563A00000200005FB3",
484 | }
485 |
486 | for _, s := range cases {
487 | buf, _ := hex.DecodeString(s)
488 | size, decoded, err := DecodeTCPFromSlice(buf)
489 | if err != nil {
490 | t.Fatal(err)
491 | }
492 | if size != len(buf) {
493 | t.Error("[DecodeTCPFromSlice] payload not fully processed")
494 | }
495 |
496 | res, err := json.Marshal(decoded.Packet)
497 | if err != nil {
498 | t.Fatal(err)
499 | }
500 | var packet Packet
501 | err = json.Unmarshal(res, &packet)
502 | if err != nil {
503 | t.Fatal(err)
504 | }
505 | }
506 | }
507 |
508 | func tcpBenchCases(n int) [][]byte {
509 | cases := []string{
510 | "000000000000003608010000016B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000010000C7CF",
511 | "000000000000002808010000016B40D9AD80010000000000000000000000000000000103021503010101425E100000010000F22A",
512 | "000000000000004308020000016B40D57B480100000000000000000000000000000001010101000000000000016B40D5C198010000000000000000000000000000000101010101000000020000252C",
513 | "000000000000005F10020000016BDBC7833000000000000000000000000000000000000B05040200010000030002000B00270042563A00000000016BDBC7871800000000000000000000000000000000000B05040200010000030002000B00260042563A00000200005FB3",
514 | "000000000000004A8E010000016B412CEE000100000000000000000000000000000000010005000100010100010011001D00010010015E2C880002000B000000003544C87A000E000000001DD7E06A00000100002994",
515 | "00000000000000A98E020000017357633410000F0DC39B2095964A00AC00F80B00000000000B000500F00100150400C800004501007156000500B5000500B600040018000000430FE00044011B000100F10000601B000000000000017357633BE1000F0DC39B2095964A00AC00F80B000001810001000000000000000000010181002D11213102030405060708090A0B0C0D0E0F104545010ABC212102030405060708090A0B0C0D0E0F10020B010AAD020000BF30",
516 | }
517 |
518 | casesBin := make([][]byte, len(cases))
519 |
520 | for i := range cases {
521 | bs, _ := hex.DecodeString(cases[i])
522 | casesBin[i] = bs
523 | }
524 |
525 | benchCases := make([][]byte, n)
526 |
527 | for i := 0; i < n; i++ {
528 | benchCases[i] = casesBin[rand.Intn(len(cases))]
529 | }
530 |
531 | return benchCases
532 | }
533 |
534 | func benchmarkTCPDecodeSlice(b *testing.B, config ...*DecodeConfig) {
535 | benchCases := tcpBenchCases(b.N)
536 |
537 | b.ResetTimer()
538 |
539 | for i := 0; i < b.N; i++ {
540 | _, _, err := DecodeTCPFromSlice(benchCases[i], config...)
541 | if err != nil {
542 | b.Fatal(err)
543 | }
544 | }
545 | }
546 |
547 | func benchmarkTCPDecodeReader(b *testing.B, config ...*DecodeConfig) {
548 | benchCases := tcpBenchCases(b.N)
549 | readers := make([]io.Reader, len(benchCases))
550 | for i, benchCase := range benchCases {
551 | readers[i] = bytes.NewReader(benchCase)
552 | }
553 | b.ResetTimer()
554 |
555 | for i := 0; i < b.N; i++ {
556 | _, _, err := DecodeTCPFromReader(readers[i], config...)
557 | if err != nil {
558 | b.Fatal(err)
559 | }
560 | }
561 | }
562 |
563 | func udpBenchCases(n int) [][]byte {
564 | cases := []string{
565 | "005FCAFE0107000F3335323039333038363430333635358E010000016B4F831C680100000000000000000000000000000000010005000100010100010011009D00010010015E2C880002000B000000003544C87A000E000000001DD7E06A000001",
566 | "003DCAFE0105000F33353230393330383634303336353508010000016B4F815B30010000000000000000000000000000000103021503010101425DBC000001",
567 | "0086CAFE0101000F3335323039333038353639383230368E0100000167EFA919800200000000000000000000000000000000FC0013000800EF0000F00000150500C80000450200010000710000FC00000900B5000000B600000042305600CD432A00CE6064001100090012FF22001303D1000F0000000200F1000059D90010000000000000000001",
568 | "0083CAFE0101000F3335323039333038353639383230368E0100000167F1AEEC00000A750E8F1D43443100F800B210000000000012000700EF0000F00000150500C800004501000100007142000900B5000600B6000500422FB300CD432A00CE60640011000700120007001303EC000F0000000200F1000059D90010000000000000000001",
569 | "01E4CAFE0126000F333532303934303839333937343634080400000163C803B420010A259E1A1D4A057D00DA0128130057421B0A4503F00150051503EF01510052005900BE00C1000AB50008B60005427025CD79D8CE605A5400005500007300005A0000C0000007C700000018F1000059D910002D32C85300000000570000000064000000F7BF000000000000000163C803AC50010A25A9D21D4A01B600DB0128130056421B0A4503F00150051503EF01510052005900BE00C1000AB50008B6000542702ECD79D8CE605A5400005500007300005A0000C0000007C700000017F1000059D910002D32B05300000000570000000064000000F7BF000000000000000163C803A868010A25B5581D49FE5400DB0127130057421B0A4503F00150051503EF01510052005900BE00C1000AB50008B60005427039CD79D8CE605A5400005500007300005A0000C0000007C700000017F1000059D910002D32995300000000570000000064000000F7BF000000000000000163C803A4B2010A25CC861D49F75C00DB0124130058421B0A4503F00150051503EF01510052005900BE00C1000AB50008B6000542703CCD79D8CE605A5400005500007300005A0000C0000007C700000018F1000059D910002D32695300000000570000000064000000F7BF000000000004",
570 | }
571 |
572 | casesBin := make([][]byte, len(cases))
573 |
574 | for i := range cases {
575 | bs, _ := hex.DecodeString(cases[i])
576 | casesBin[i] = bs
577 | }
578 |
579 | benchCases := make([][]byte, n)
580 |
581 | for i := 0; i < n; i++ {
582 | benchCases[i] = casesBin[rand.Intn(len(cases))]
583 | }
584 |
585 | return benchCases
586 | }
587 |
588 | func benchmarkUDPDecodeSlice(b *testing.B, config ...*DecodeConfig) {
589 | benchCases := udpBenchCases(b.N)
590 | b.ResetTimer()
591 |
592 | for i := 0; i < b.N; i++ {
593 | _, _, err := DecodeUDPFromSlice(benchCases[i], config...)
594 | if err != nil {
595 | b.Fatal(err)
596 | }
597 | }
598 | }
599 |
600 | func benchmarkUDPDecodeReader(b *testing.B, config ...*DecodeConfig) {
601 | benchCases := udpBenchCases(b.N)
602 | readers := make([]io.Reader, len(benchCases))
603 | for i, benchCase := range benchCases {
604 | readers[i] = bytes.NewReader(benchCase)
605 | }
606 | b.ResetTimer()
607 |
608 | for i := 0; i < b.N; i++ {
609 | _, _, err := DecodeUDPFromReader(readers[i], config...)
610 | if err != nil {
611 | b.Fatal(err)
612 | }
613 | }
614 | }
615 |
616 | func BenchmarkTCPDecode(b *testing.B) {
617 | benchmarkTCPDecodeSlice(b)
618 | }
619 |
620 | func BenchmarkTCPDecodeReader(b *testing.B) {
621 | benchmarkTCPDecodeReader(b)
622 | }
623 |
624 | func BenchmarkUDPDecodeSlice(b *testing.B) {
625 | benchmarkUDPDecodeSlice(b)
626 | }
627 |
628 | func BenchmarkUDPDecodeReader(b *testing.B) {
629 | benchmarkUDPDecodeReader(b)
630 | }
631 |
632 | func BenchmarkTCPDecodeAllocElementsOnReadBuffer(b *testing.B) {
633 | benchmarkTCPDecodeSlice(b, &DecodeConfig{OnReadBuffer})
634 | }
635 |
636 | func BenchmarkTCPDecodeReaderAllocElementsOnReadBuffer(b *testing.B) {
637 | benchmarkTCPDecodeReader(b, &DecodeConfig{OnReadBuffer})
638 | }
639 |
640 | func BenchmarkUDPDecodeSliceAllocElementsOnReadBuffer(b *testing.B) {
641 | benchmarkUDPDecodeSlice(b, &DecodeConfig{OnReadBuffer})
642 | }
643 |
644 | func BenchmarkUDPDecodeReaderAllocElementsOnReadBuffer(b *testing.B) {
645 | benchmarkUDPDecodeReader(b, &DecodeConfig{OnReadBuffer})
646 | }
647 |
648 | func BenchmarkEncodeTCP(b *testing.B) {
649 | cases := []*Packet{
650 | {
651 | CodecID: Codec12,
652 | Data: nil,
653 | Messages: []Message{{Type: TypeCommand, Text: "getinfo"}},
654 | },
655 | {
656 | CodecID: Codec12,
657 | Data: nil,
658 | Messages: []Message{{Type: TypeCommand, Text: "getio"}},
659 | },
660 | {
661 | CodecID: Codec12,
662 | Data: nil,
663 | Messages: []Message{{Type: TypeCommand, Text: "getver"}},
664 | },
665 | {
666 | CodecID: Codec13,
667 | Data: nil,
668 | Messages: []Message{{Type: TypeResponse, Text: "getver", Timestamp: uint32(time.Now().Unix())}},
669 | },
670 | {
671 | CodecID: Codec13,
672 | Data: nil,
673 | Messages: []Message{{Type: TypeResponse, Text: "getinfo", Timestamp: uint32(time.Now().Unix())}},
674 | },
675 | {
676 | CodecID: Codec14,
677 | Data: nil,
678 | Messages: []Message{{Type: TypeCommand, Text: "getver", Imei: "352093081452251"}},
679 | },
680 | {
681 | CodecID: Codec14,
682 | Data: nil,
683 | Messages: []Message{{Type: TypeCommand, Text: "getio", Imei: "352093081452251"}},
684 | },
685 | }
686 |
687 | benchCase := make([]*Packet, b.N)
688 |
689 | for i := 0; i < b.N; i++ {
690 | benchCase[i] = cases[rand.Intn(len(cases))]
691 | }
692 |
693 | b.ResetTimer()
694 |
695 | for i := 0; i < b.N; i++ {
696 | _, err := EncodePacketTCP(benchCase[i])
697 | if err != nil {
698 | b.Fatal(err)
699 | }
700 | }
701 | }
702 |
703 | func BenchmarkEncodeUDP(b *testing.B) {
704 | cases := []*Packet{
705 | {
706 | CodecID: Codec12,
707 | Data: nil,
708 | Messages: []Message{{Type: TypeCommand, Text: "getinfo"}},
709 | },
710 | {
711 | CodecID: Codec12,
712 | Data: nil,
713 | Messages: []Message{{Type: TypeCommand, Text: "getio"}},
714 | },
715 | {
716 | CodecID: Codec12,
717 | Data: nil,
718 | Messages: []Message{{Type: TypeCommand, Text: "getver"}},
719 | },
720 | {
721 | CodecID: Codec13,
722 | Data: nil,
723 | Messages: []Message{{Type: TypeResponse, Text: "getver", Timestamp: uint32(time.Now().Unix())}},
724 | },
725 | {
726 | CodecID: Codec13,
727 | Data: nil,
728 | Messages: []Message{{Type: TypeResponse, Text: "getinfo", Timestamp: uint32(time.Now().Unix())}},
729 | },
730 | {
731 | CodecID: Codec14,
732 | Data: nil,
733 | Messages: []Message{{Type: TypeCommand, Text: "getver", Imei: "352093081452251"}},
734 | },
735 | {
736 | CodecID: Codec14,
737 | Data: nil,
738 | Messages: []Message{{Type: TypeCommand, Text: "getio", Imei: "352093081452251"}},
739 | },
740 | }
741 |
742 | benchCase := make([]*Packet, b.N)
743 |
744 | for i := 0; i < b.N; i++ {
745 | benchCase[i] = cases[rand.Intn(len(cases))]
746 | }
747 |
748 | b.ResetTimer()
749 |
750 | for i := 0; i < b.N; i++ {
751 | _, err := EncodePacketUDP("352093081452251", 0, 0, benchCase[i])
752 | if err != nil {
753 | b.Fatal(err)
754 | }
755 | }
756 | }
757 |
758 | func BenchmarkCrc16IBMGenerateLookupTable(b *testing.B) {
759 | // save results here to avoid result table allocation on the stack
760 | // and possibly some optimizations.
761 | results := make([][]uint16, 256)
762 | b.ResetTimer()
763 | for i := 0; i < b.N; i++ {
764 | results[i%256] = genCrc16IBMLookupTable()
765 | }
766 | }
767 |
768 | func BenchmarkCrc16IBMWithLookupTable(b *testing.B) {
769 | benchCases := make([][]byte, 256)
770 | for i := range benchCases {
771 | benchCases[i] = make([]byte, 1024)
772 | rand.Read(benchCases[i])
773 | }
774 | b.ResetTimer()
775 |
776 | for i := 0; i < b.N; i++ {
777 | Crc16IBM(benchCases[i%256])
778 | }
779 | }
780 |
781 | func BenchmarkCrc16IBMWithoutLookupTable(b *testing.B) {
782 | benchCases := make([][]byte, 256)
783 | for i := range benchCases {
784 | benchCases[i] = make([]byte, 1024)
785 | rand.Read(benchCases[i])
786 | }
787 | b.ResetTimer()
788 |
789 | for i := 0; i < b.N; i++ {
790 | crc16IBMNoTable(benchCases[i%256])
791 | }
792 | }
793 |
794 | func crc16IBMNoTable(data []byte) uint16 {
795 | crc := uint16(0)
796 | size := len(data)
797 | for i := 0; i < size; i++ {
798 | crc ^= uint16(data[i])
799 | for j := 0; j < 8; j++ {
800 | if (crc & 0x0001) == 1 {
801 | crc = (crc >> 1) ^ 0xA001
802 | } else {
803 | crc >>= 1
804 | }
805 | }
806 | }
807 | return crc
808 | }
809 |
--------------------------------------------------------------------------------
/teltonika.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Alim Zanibekov
2 | //
3 | // Use of this source code is governed by an MIT-style
4 | // license that can be found in the LICENSE file or at
5 | // https://opensource.org/licenses/MIT.
6 |
7 | package teltonika
8 |
9 | import (
10 | "encoding/binary"
11 | "encoding/hex"
12 | "errors"
13 | "fmt"
14 | "io"
15 | "strings"
16 | )
17 |
18 | type GenerationType uint8
19 |
20 | //goland:noinspection GoUnusedConst
21 | const (
22 | OnExit GenerationType = iota // 0
23 | OnEntrance // 1
24 | OnBoth // 2
25 | Reserved // 3
26 | Hysteresis // 4
27 | OnChange // 5
28 | Eventual // 6
29 | Periodical // 7
30 | Unknown GenerationType = 0xFF // 255
31 | )
32 |
33 | type CodecId uint8
34 |
35 | const (
36 | Codec8 CodecId = 0x08
37 | Codec8E CodecId = 0x8E
38 | Codec16 CodecId = 0x10
39 | Codec12 CodecId = 0x0C
40 | Codec13 CodecId = 0x0D
41 | Codec14 CodecId = 0x0E
42 | Codec15 CodecId = 0x0F
43 | )
44 |
45 | type MessageType uint8
46 |
47 | const (
48 | TypeCommand MessageType = 0x05
49 | TypeResponse MessageType = 0x06
50 | TypeNotExecuted MessageType = 0x11
51 | )
52 |
53 | type IOElementsAlloc uint8
54 |
55 | //goland:noinspection GoUnusedConst
56 | const (
57 | OnHeap IOElementsAlloc = iota // Alloc IOElement->Value on heap (make([]byte, x))
58 | OnReadBuffer // IOElement->Value = readBuffer[x:y]
59 | )
60 |
61 | type PacketResponse []byte
62 |
63 | type DecodedUDP struct {
64 | PacketId uint16 `json:"packetId"`
65 | AvlPacketId uint8 `json:"avlPacketId"`
66 | Imei string `json:"imei"`
67 | Packet *Packet `json:"packet"`
68 | Response PacketResponse `json:"response"`
69 | }
70 |
71 | type DecodedTCP struct {
72 | Packet *Packet `json:"packet"`
73 | Response PacketResponse `json:"response"`
74 | }
75 |
76 | type Packet struct {
77 | CodecID CodecId `json:"codecId"`
78 | Data []Data `json:"data,omitempty"`
79 | Messages []Message `json:"messages,omitempty"`
80 | }
81 |
82 | type Data struct {
83 | TimestampMs uint64 `json:"timestampMs"`
84 | Lng float64 `json:"lng"`
85 | Lat float64 `json:"lat"`
86 | Altitude int16 `json:"altitude"`
87 | Angle uint16 `json:"angle"`
88 | EventID uint16 `json:"event_id"`
89 | Speed uint16 `json:"speed"`
90 | Satellites uint8 `json:"satellites"`
91 | Priority uint8 `json:"priority"`
92 | GenerationType GenerationType `json:"generationType"` // codec 16 else Unknown
93 | Elements []IOElement `json:"elements"`
94 | }
95 |
96 | type IOElementValue []byte
97 |
98 | type IOElement struct {
99 | Id uint16 `json:"id"`
100 | Value IOElementValue `json:"value"`
101 | }
102 |
103 | type Message struct {
104 | Timestamp uint32 `json:"timestamp,omitempty"` // if codec is 13 or 15 else 0
105 | Type MessageType `json:"type"` // may contain an arbitrary value if codec is 15
106 | Imei string `json:"imei,omitempty"` // if codec is 14 or 15 else ""
107 | Text string `json:"text"`
108 | }
109 |
110 | // DecodeConfig optional configuration that can be passed in all Decode* functions (last param).
111 | // By default, used - DecodeConfig { IoElementsAlloc: OnHeap }
112 | type DecodeConfig struct {
113 | IoElementsAlloc IOElementsAlloc // IOElement->Value allocation mode: `OnHeap` or `OnReadBuffer`
114 | }
115 |
116 | var defaultDecodeConfig = &DecodeConfig{
117 | IoElementsAlloc: OnHeap,
118 | }
119 |
120 | func (r IOElementValue) MarshalJSON() ([]byte, error) {
121 | return []byte(`"` + hex.EncodeToString(r) + `"`), nil
122 | }
123 |
124 | func (r IOElementValue) UnmarshalJSON(data []byte) error {
125 | _, err := hex.Decode(data, r)
126 | return err
127 | }
128 |
129 | func (r PacketResponse) MarshalJSON() ([]byte, error) {
130 | return []byte(`"` + hex.EncodeToString(r) + `"`), nil
131 | }
132 |
133 | func (r PacketResponse) UnmarshalJSON(data []byte) error {
134 | _, err := hex.Decode(data, r)
135 | return err
136 | }
137 |
138 | func (r *GenerationType) MarshalJSON() ([]byte, error) {
139 | switch *r {
140 | case OnExit:
141 | return []byte(`"OnExit"`), nil
142 | case OnEntrance:
143 | return []byte(`"OnEntrance"`), nil
144 | case OnBoth:
145 | return []byte(`"OnBoth"`), nil
146 | case Reserved:
147 | return []byte(`"Reserved"`), nil
148 | case Hysteresis:
149 | return []byte(`"Hysteresis"`), nil
150 | case OnChange:
151 | return []byte(`"OnChange"`), nil
152 | case Eventual:
153 | return []byte(`"Eventual"`), nil
154 | case Periodical:
155 | return []byte(`"Periodical"`), nil
156 | case Unknown:
157 | return []byte(`"Unknown"`), nil
158 | default:
159 | return nil, fmt.Errorf("unknown generation type %d", r)
160 | }
161 | }
162 |
163 | func (r *GenerationType) UnmarshalJSON(data []byte) error {
164 | if len(data) < 4 {
165 | return fmt.Errorf("unknown generation type '%s'", string(data))
166 | }
167 | key := string(data[1 : len(data)-1])
168 | switch key {
169 | case "OnExit":
170 | *r = OnExit
171 | case "OnEntrance":
172 | *r = OnEntrance
173 | case "OnBoth":
174 | *r = OnBoth
175 | case "Reserved":
176 | *r = Reserved
177 | case "Hysteresis":
178 | *r = Hysteresis
179 | case "OnChange":
180 | *r = OnChange
181 | case "Eventual":
182 | *r = Eventual
183 | case "Periodical":
184 | *r = Periodical
185 | case "Unknown":
186 | *r = Unknown
187 | default:
188 | return fmt.Errorf("unknown generation type '%s'", key)
189 | }
190 | return nil
191 | }
192 |
193 | // DecodeTCPFromSlice
194 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) tcp packet from slice
195 | // returns the number of bytes read from 'inputBuffer' and decoded packet or an error
196 | func DecodeTCPFromSlice(inputBuffer []byte, config ...*DecodeConfig) (int, *DecodedTCP, error) {
197 | if len(config) > 1 {
198 | return 0, nil, fmt.Errorf("too many arguments specified")
199 | }
200 | cfg := defaultDecodeConfig
201 | if len(config) == 1 && config[0] != nil {
202 | cfg = config[0]
203 | }
204 | buf, packet, err := decodeTCPInternal(nil, inputBuffer, nil, cfg)
205 | if err != nil {
206 | return 0, nil, err
207 | }
208 | return len(buf), packet, nil
209 | }
210 |
211 | // DecodeTCPFromReader
212 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) tcp packet from io.Reader
213 | // returns decoded packet or an error
214 | func DecodeTCPFromReader(input io.Reader, config ...*DecodeConfig) ([]byte, *DecodedTCP, error) {
215 | if len(config) > 1 {
216 | return nil, nil, fmt.Errorf("too many arguments specified")
217 | }
218 | cfg := defaultDecodeConfig
219 | if len(config) == 1 && config[0] != nil {
220 | cfg = config[0]
221 | }
222 | return decodeTCPInternal(input, nil, nil, cfg)
223 | }
224 |
225 | // DecodeTCPFromReaderBuf
226 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) tcp packet from io.Reader
227 | // writes the read bytes to readBytes buffer (max packet size 1280 bytes)
228 | // returns the number of bytes read and decoded packet or an error
229 | func DecodeTCPFromReaderBuf(input io.Reader, readBytes []byte, config ...*DecodeConfig) (int, *DecodedTCP, error) {
230 | if len(config) > 1 {
231 | return 0, nil, fmt.Errorf("too many arguments specified")
232 | }
233 | if readBytes == nil {
234 | return 0, nil, fmt.Errorf("output readBytes is nil, use DecodeTCPFromReader if you dont want use fixed buffer to read")
235 | }
236 | cfg := defaultDecodeConfig
237 | if len(config) == 1 && config[0] != nil {
238 | cfg = config[0]
239 | }
240 | buf, packet, err := decodeTCPInternal(input, nil, readBytes, cfg)
241 | return len(buf), packet, err
242 | }
243 |
244 | // DecodeUDPFromSlice
245 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) udp packet from slice
246 | // returns the number of bytes read from 'inputBuffer' and decoded packet or an error
247 | func DecodeUDPFromSlice(inputBuffer []byte, config ...*DecodeConfig) (int, *DecodedUDP, error) {
248 | if len(config) > 1 {
249 | return 0, nil, fmt.Errorf("too many arguments specified")
250 | }
251 | cfg := defaultDecodeConfig
252 | if len(config) == 1 && config[0] != nil {
253 | cfg = config[0]
254 | }
255 | buf, packet, err := decodeUDPInternal(nil, inputBuffer, nil, cfg)
256 | if err != nil {
257 | return 0, nil, err
258 | }
259 | return len(buf), packet, nil
260 | }
261 |
262 | // DecodeUDPFromReader
263 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) udp packet from io.Reader
264 | // returns the read buffer and decoded packet or an error
265 | func DecodeUDPFromReader(input io.Reader, config ...*DecodeConfig) ([]byte, *DecodedUDP, error) {
266 | if len(config) > 1 {
267 | return nil, nil, fmt.Errorf("too many arguments specified")
268 | }
269 |
270 | cfg := defaultDecodeConfig
271 | if len(config) == 1 && config[0] != nil {
272 | cfg = config[0]
273 | }
274 | return decodeUDPInternal(input, nil, nil, cfg)
275 | }
276 |
277 | // DecodeUDPFromReaderBuf
278 | // decode (12, 13, 14, 15, 8, 16, or 8 extended codec) udp packet from io.Reader
279 | // writes read bytes to readBytes slice (max packet size 1280 bytes)
280 | // returns the number of bytes read and decoded packet or an error
281 | func DecodeUDPFromReaderBuf(input io.Reader, readBytes []byte, config ...*DecodeConfig) (int, *DecodedUDP, error) {
282 | if len(config) > 1 {
283 | return 0, nil, fmt.Errorf("too many arguments specified")
284 | }
285 | if readBytes == nil {
286 | return 0, nil, fmt.Errorf("output readBytes is nil, use DecodeUDPFromReader if you don't want use fixed buffer to read")
287 | }
288 | cfg := defaultDecodeConfig
289 | if len(config) == 1 && config[0] != nil {
290 | cfg = config[0]
291 | }
292 | buf, packet, err := decodeUDPInternal(input, nil, readBytes, cfg)
293 | return len(buf), packet, err
294 | }
295 |
296 | // EncodePacketTCP
297 | // encode packet (12, 13, 14, 15, 8, 16, or 8 extended codec)
298 | // returns an array of bytes with encoded data or an error
299 | // note: implementations for 8, 16, 8E are practically not needed, they are made only for testing
300 | func EncodePacketTCP(packet *Packet) ([]byte, error) {
301 | return encodeTCPInternal(packet)
302 | }
303 |
304 | // EncodePacketUDP
305 | // encode packet (12, 13, 14, 15, 8, 16, or 8 extended codec)
306 | // returns an array of bytes with encoded data or an error
307 | // note: implementations for 8, 16, 8E are practically not needed, they are made only for testing
308 | func EncodePacketUDP(imei string, packetId uint16, avlPacketId uint8, packet *Packet) ([]byte, error) {
309 | return encodeUDPInternal(imei, packetId, avlPacketId, packet)
310 | }
311 |
312 | func decodeTCPInternal(inputReader io.Reader, inputBuffer []byte, outputBuffer []byte, config *DecodeConfig) ([]byte, *DecodedTCP, error) {
313 | const headerSize = 8
314 | var err error
315 | var header []byte
316 | if inputBuffer == nil {
317 | if outputBuffer != nil {
318 | if len(outputBuffer) < headerSize {
319 | return nil, nil, fmt.Errorf("output buffer size lower than %v bytes, unable to read header", headerSize)
320 | }
321 | header = outputBuffer[:headerSize]
322 | } else {
323 | header = make([]byte, headerSize)
324 | }
325 |
326 | if err = readFromReader(inputReader, header); err != nil {
327 | return nil, nil, err
328 | }
329 | } else {
330 | if len(inputBuffer) < headerSize {
331 | return nil, nil, fmt.Errorf("input buffer size lower than %v bytes, unable to read header", headerSize)
332 | }
333 | header = inputBuffer[:headerSize]
334 | }
335 |
336 | preamble := int(binary.BigEndian.Uint32(header[:4]))
337 |
338 | if preamble != 0 {
339 | return nil, nil, fmt.Errorf("'Preamble' field must be equal to 0. received preamble is %v", preamble)
340 | }
341 |
342 | // Maximum AVL packet size is 1280 bytes. (Is it with or without CRC, Preamble, Data Field Length?)
343 | dataFieldLength := int(binary.BigEndian.Uint32(header[4:]))
344 |
345 | if dataFieldLength > 1280 {
346 | return nil, nil, fmt.Errorf("maximum AVL packet size is 1280 bytes. 'Data Field Length' equal to %v bytes", dataFieldLength)
347 | }
348 |
349 | remainingSize := dataFieldLength + 4 // + CRC size
350 | packetSize := remainingSize + headerSize
351 |
352 | var buffer []byte
353 | if inputBuffer == nil {
354 | if outputBuffer != nil {
355 | if len(outputBuffer) < packetSize {
356 | return nil, nil, fmt.Errorf("output buffer size lower than specified in packet. specified: %v, output size %v bytes", packetSize, len(outputBuffer))
357 | }
358 | buffer = outputBuffer[:packetSize]
359 | } else {
360 | buffer = make([]byte, packetSize)
361 | copy(buffer, header)
362 | }
363 |
364 | if err = readFromReader(inputReader, buffer[headerSize:]); err != nil {
365 | return nil, nil, err
366 | }
367 | } else {
368 | if len(inputBuffer) < packetSize {
369 | return nil, nil, fmt.Errorf("input buffer size lower than specified in packet. specified: %v, input size %v bytes", packetSize, len(inputBuffer))
370 | }
371 | buffer = inputBuffer[:packetSize]
372 | }
373 |
374 | reader := newByteReader(buffer[headerSize:], config.IoElementsAlloc == OnHeap)
375 |
376 | packet := &DecodedTCP{
377 | Packet: &Packet{},
378 | }
379 |
380 | if err = decodePacket(reader, packet.Packet); err != nil {
381 | return nil, nil, err
382 | }
383 |
384 | crcCalc := Crc16IBM(reader.input[:reader.pos])
385 |
386 | crc, err := reader.ReadUInt32BE()
387 | if err != nil {
388 | return nil, nil, err
389 | }
390 |
391 | if uint32(crcCalc) != crc {
392 | return nil, nil, fmt.Errorf("calculated CRC-16 sum '%08X' is not equal to control CRC-16 sum '%08X'", crcCalc, crc)
393 | }
394 |
395 | if !isCMDCodecId(uint8(packet.Packet.CodecID)) {
396 | packet.Response = []byte{0x00, 0x00, 0x00, uint8(len(packet.Packet.Data))}
397 | }
398 |
399 | return buffer, packet, nil
400 | }
401 |
402 | func decodeUDPInternal(inputReader io.Reader, inputBuffer []byte, outputBuffer []byte, config *DecodeConfig) ([]byte, *DecodedUDP, error) {
403 | const headerSize = 5
404 | var err error
405 | var header []byte
406 | if inputBuffer == nil {
407 | if outputBuffer != nil {
408 | if len(outputBuffer) < headerSize {
409 | return nil, nil, fmt.Errorf("output buffer size lower than %v bytes, unable to read header", headerSize)
410 | }
411 | header = outputBuffer[:headerSize]
412 | } else {
413 | header = make([]byte, headerSize)
414 | }
415 | if err = readFromReader(inputReader, header); err != nil {
416 | return nil, nil, err
417 | }
418 | } else {
419 | if len(inputBuffer) < headerSize {
420 | return nil, nil, fmt.Errorf("input buffer size lower than %v bytes, unable to read header", headerSize)
421 | }
422 | header = inputBuffer[:headerSize]
423 | }
424 |
425 | size := int(binary.BigEndian.Uint16(header[:2]))
426 |
427 | packetId := binary.BigEndian.Uint16(header[2:])
428 |
429 | // Maximum AVL packet size is 1280 bytes.
430 | if size > 1280 {
431 | return nil, nil, fmt.Errorf("maximum AVL packet size is 1280 bytes. 'Data Field Length' equal to %v bytes", size)
432 | }
433 |
434 | remainingSize := size - 3
435 | packetSize := remainingSize + headerSize
436 |
437 | var buffer []byte
438 | if inputBuffer == nil {
439 | if outputBuffer != nil {
440 | if len(outputBuffer) < packetSize {
441 | return nil, nil, fmt.Errorf("output buffer size lower than specified in packet. specified: %v, output size %v bytes", packetSize, len(outputBuffer))
442 | }
443 | buffer = outputBuffer[:packetSize]
444 | } else {
445 | buffer = make([]byte, packetSize)
446 | copy(buffer, header)
447 | }
448 |
449 | if err = readFromReader(inputReader, buffer[headerSize:]); err != nil {
450 | return nil, nil, err
451 | }
452 | } else {
453 | if len(inputBuffer) < packetSize {
454 | return nil, nil, fmt.Errorf("input buffer size lower than specified in packet. specified: %v, input size %v bytes", packetSize, len(inputBuffer))
455 | }
456 | buffer = inputBuffer[:packetSize]
457 | }
458 |
459 | reader := newByteReader(buffer[headerSize:], config.IoElementsAlloc == OnHeap)
460 |
461 | avlPacketId, err := reader.ReadUInt8BE()
462 | if err != nil {
463 | return nil, nil, err
464 | }
465 |
466 | imeiLen, err := reader.ReadUInt16BE()
467 | if err != nil {
468 | return nil, nil, err
469 | }
470 |
471 | imei, err := reader.ReadBytes(int(imeiLen))
472 | if err != nil {
473 | return nil, nil, err
474 | }
475 |
476 | packet := &DecodedUDP{
477 | PacketId: packetId,
478 | AvlPacketId: avlPacketId,
479 | Imei: string(imei),
480 | Packet: &Packet{},
481 | }
482 |
483 | if err = decodePacket(reader, packet.Packet); err != nil {
484 | return nil, nil, err
485 | }
486 |
487 | if !isCMDCodecId(uint8(packet.Packet.CodecID)) {
488 | packet.Response = []byte{
489 | 0x00, // Length
490 | 0x05, // Length
491 | uint8(packetId >> 8), // Packet ID
492 | uint8(packetId), // Packet ID
493 | 0x01, // Not usable byte
494 | avlPacketId, // AVL packet ID
495 | uint8(len(packet.Packet.Data)), // Number of Accepted Data
496 | }
497 | }
498 |
499 | return buffer, packet, nil
500 | }
501 |
502 | func decodePacket(reader *byteReader, packet *Packet) error {
503 | codecId, err := reader.ReadUInt8BE()
504 | if err != nil {
505 | return err
506 | }
507 | dataCount, err := reader.ReadUInt8BE()
508 | if err != nil {
509 | return err
510 | }
511 |
512 | if !isCodecSupported(codecId) {
513 | return fmt.Errorf("codec %d is not supported", codecId)
514 | }
515 |
516 | packet.CodecID = CodecId(codecId)
517 |
518 | if isCMDCodecId(codecId) {
519 | packet.Messages = make([]Message, dataCount)
520 | for i := 0; i < int(dataCount); i++ {
521 | if err = decodeCommand(packet.CodecID, reader, &packet.Messages[i]); err != nil {
522 | return err
523 | }
524 | }
525 | } else {
526 | packet.Data = make([]Data, dataCount)
527 | for i := 0; i < int(dataCount); i++ {
528 | if err = decodeData(packet.CodecID, reader, &packet.Data[i]); err != nil {
529 | return err
530 | }
531 | }
532 | }
533 |
534 | dataCountCheck, err := reader.ReadUInt8BE()
535 | if err != nil {
536 | return err
537 | }
538 |
539 | if dataCountCheck != dataCount {
540 | return fmt.Errorf("'Number of Data 1' is not equal to 'Number of Data 2'. %v != %v", dataCount, dataCountCheck)
541 | }
542 |
543 | return nil
544 | }
545 |
546 | func decodeData(codecId CodecId, reader *byteReader, data *Data) error {
547 | timestampMs, err := reader.ReadUInt64BE()
548 | if err != nil {
549 | return err
550 | }
551 | priority, err := reader.ReadUInt8BE()
552 | if err != nil {
553 | return err
554 | }
555 | lng, err := reader.ReadInt32BE()
556 | if err != nil {
557 | return err
558 | }
559 | lat, err := reader.ReadInt32BE()
560 | if err != nil {
561 | return err
562 | }
563 | altitude, err := reader.ReadInt16BE()
564 | if err != nil {
565 | return err
566 | }
567 | angle, err := reader.ReadUInt16BE()
568 | if err != nil {
569 | return err
570 | }
571 | satellites, err := reader.ReadUInt8BE()
572 | if err != nil {
573 | return err
574 | }
575 | speed, err := reader.ReadUInt16BE()
576 | if err != nil {
577 | return err
578 | }
579 |
580 | data.TimestampMs = timestampMs
581 | data.Lng = float64(lng) / 10000000.0
582 | data.Lat = float64(lat) / 10000000.0
583 | data.Altitude = altitude
584 | data.Angle = angle
585 | data.Speed = speed
586 | data.Satellites = satellites
587 | data.Priority = priority
588 |
589 | if codecId == Codec8 {
590 | return decodeElementsCodec8(reader, data)
591 | } else if codecId == Codec8E {
592 | return decodeElementsCodec8E(reader, data)
593 | } else if codecId == Codec16 {
594 | return decodeElementsCodec16(reader, data)
595 | }
596 |
597 | return fmt.Errorf("unknown codec %d", codecId)
598 | }
599 |
600 | func decodeCommand(codecId CodecId, reader *byteReader, data *Message) error {
601 | commandType, err := reader.ReadUInt8BE()
602 | if err != nil {
603 | return err
604 | }
605 |
606 | if codecId == Codec12 && commandType != uint8(TypeResponse) && commandType != uint8(TypeCommand) ||
607 | codecId == Codec13 && commandType != uint8(TypeResponse) ||
608 | codecId == Codec14 && commandType != uint8(TypeResponse) && commandType != uint8(TypeCommand) && commandType != uint8(TypeNotExecuted) {
609 | return fmt.Errorf("message type 0x%X is not supported with codec %d", commandType, codecId)
610 | }
611 |
612 | data.Type = MessageType(commandType)
613 |
614 | commandSize, err := reader.ReadUInt32BE()
615 | if err != nil {
616 | return err
617 | }
618 |
619 | if codecId == Codec13 || codecId == Codec15 {
620 | timestamp, err := reader.ReadUInt32BE()
621 | if err != nil {
622 | return err
623 | }
624 | data.Timestamp = timestamp
625 | commandSize -= 4
626 | }
627 | if codecId == Codec14 || codecId == Codec15 {
628 | imei, err := reader.ReadBytes(8)
629 | if err != nil {
630 | return err
631 | }
632 | data.Imei = strings.TrimLeft(hex.EncodeToString(imei), "0")
633 | commandSize -= 8
634 | }
635 |
636 | command, err := reader.ReadBytes(int(commandSize))
637 | if err != nil {
638 | return err
639 | }
640 | data.Text = string(command)
641 | return nil
642 | }
643 |
644 | func decodeElementsCodec8(reader *byteReader, data *Data) error {
645 | eventId, err := reader.ReadUInt8BE()
646 | if err != nil {
647 | return err
648 | }
649 |
650 | ioCount, err := reader.ReadUInt8BE()
651 | if err != nil {
652 | return err
653 | }
654 |
655 | data.GenerationType = Unknown
656 | data.EventID = uint16(eventId)
657 | data.Elements = make([]IOElement, ioCount)
658 |
659 | var n uint8
660 | var id uint8
661 | var value []byte
662 | var k = 0
663 |
664 | for i := 1; i <= 8; i *= 2 {
665 | n, err = reader.ReadUInt8BE()
666 | if err != nil {
667 | return err
668 | }
669 | for j := 0; j < int(n); j++ {
670 | id, err = reader.ReadUInt8BE()
671 | if err != nil {
672 | return err
673 | }
674 | value, err = reader.ReadBytes(i)
675 | if err != nil {
676 | return err
677 | }
678 | if k >= int(ioCount) {
679 | return fmt.Errorf("too many i/o elements, expected at most %d, found %d", ioCount, k)
680 | }
681 | data.Elements[k] = IOElement{
682 | Id: uint16(id),
683 | Value: value,
684 | }
685 | k++
686 | }
687 | }
688 |
689 | return nil
690 | }
691 |
692 | func decodeElementsCodec16(reader *byteReader, data *Data) error {
693 | eventId, err := reader.ReadUInt16BE()
694 | if err != nil {
695 | return err
696 | }
697 |
698 | generationType, err := reader.ReadUInt8BE()
699 | if err != nil {
700 | return err
701 | }
702 |
703 | ioCount, err := reader.ReadUInt8BE()
704 | if err != nil {
705 | return err
706 | }
707 |
708 | if generationType > 7 {
709 | return fmt.Errorf("invalid generation type, must be number from 0 to 7, got %v", generationType)
710 | }
711 |
712 | data.GenerationType = GenerationType(generationType)
713 | data.EventID = eventId
714 | data.Elements = make([]IOElement, ioCount)
715 |
716 | var n uint8
717 | var id uint16
718 | var value []byte
719 | var k = 0
720 |
721 | for i := 1; i <= 8; i *= 2 {
722 | n, err = reader.ReadUInt8BE()
723 | if err != nil {
724 | return err
725 | }
726 | for j := 0; j < int(n); j++ {
727 | id, err = reader.ReadUInt16BE()
728 | if err != nil {
729 | return err
730 | }
731 | value, err = reader.ReadBytes(i)
732 | if err != nil {
733 | return err
734 | }
735 | if k >= int(ioCount) {
736 | return fmt.Errorf("too many i/o elements, expected at most %d, found %d", ioCount, k)
737 | }
738 | data.Elements[k] = IOElement{
739 | Id: id,
740 | Value: value,
741 | }
742 | k++
743 | }
744 | }
745 |
746 | return nil
747 | }
748 |
749 | func decodeElementsCodec8E(reader *byteReader, data *Data) error {
750 | eventId, err := reader.ReadUInt16BE()
751 | if err != nil {
752 | return err
753 | }
754 |
755 | ioCount, err := reader.ReadUInt16BE()
756 | if err != nil {
757 | return err
758 | }
759 |
760 | data.GenerationType = Unknown
761 | data.EventID = eventId
762 | data.Elements = make([]IOElement, ioCount)
763 |
764 | var n uint16
765 | var id uint16
766 | var value []byte
767 | var k = 0
768 |
769 | for i := 1; i <= 8; i *= 2 {
770 | n, err = reader.ReadUInt16BE()
771 | if err != nil {
772 | return err
773 | }
774 | for j := 0; j < int(n); j++ {
775 | id, err = reader.ReadUInt16BE()
776 | if err != nil {
777 | return err
778 | }
779 | value, err = reader.ReadBytes(i)
780 | if err != nil {
781 | return err
782 | }
783 | if k >= int(ioCount) {
784 | return fmt.Errorf("too many i/o elements, expected at most %d, found %d", ioCount, k)
785 | }
786 | data.Elements[k] = IOElement{
787 | Id: id,
788 | Value: value,
789 | }
790 | k++
791 | }
792 | }
793 |
794 | var length uint16
795 | var ioCountNX uint16
796 |
797 | ioCountNX, err = reader.ReadUInt16BE()
798 | if err != nil {
799 | return err
800 | }
801 |
802 | for i := 0; i < int(ioCountNX); i++ {
803 | id, err = reader.ReadUInt16BE()
804 | if err != nil {
805 | return err
806 | }
807 | length, err = reader.ReadUInt16BE()
808 | if err != nil {
809 | return err
810 | }
811 | value, err = reader.ReadBytes(int(length))
812 | if err != nil {
813 | return err
814 | }
815 | if k >= int(ioCount) {
816 | return fmt.Errorf("too many i/o elements, expected at most %d, found %d", ioCount, i)
817 | }
818 | data.Elements[k] = IOElement{
819 | Id: id,
820 | Value: value,
821 | }
822 | k++
823 | }
824 |
825 | return nil
826 | }
827 |
828 | func encodeTCPInternal(packet *Packet) ([]byte, error) {
829 | if !isCodecSupported(uint8(packet.CodecID)) {
830 | return nil, fmt.Errorf("codec %d is not supported", packet.CodecID)
831 | }
832 | if packet.Messages != nil && packet.Data != nil {
833 | return nil, fmt.Errorf("invalid packet. Only one of packet.Message, packet.Data should contain data")
834 | }
835 | if isCMDCodecId(uint8(packet.CodecID)) && packet.Messages == nil {
836 | return nil, fmt.Errorf("nothing to encode, packet.Messages is nil")
837 | }
838 | if !isCMDCodecId(uint8(packet.CodecID)) && packet.Data == nil {
839 | return nil, fmt.Errorf("nothing to encode, packet.Data is nil")
840 | }
841 |
842 | dataSize := 3 // packet fields size
843 | if isCMDCodecId(uint8(packet.CodecID)) {
844 | for _, command := range packet.Messages {
845 | dataSize += len(command.Text) + 5
846 | if packet.CodecID == Codec14 {
847 | dataSize += 8
848 | } else if packet.CodecID == Codec13 {
849 | dataSize += 4
850 | } else if packet.CodecID == Codec15 {
851 | dataSize += 12
852 | }
853 | }
854 | } else {
855 | var n int
856 | var err error
857 | for _, data := range packet.Data {
858 | dataSize += 24 // data fields size
859 | if packet.CodecID == Codec8 {
860 | n, err = calculateElementsSizeCodec8(data.Elements)
861 | } else if packet.CodecID == Codec8E {
862 | n, err = calculateElementsSizeCodec8E(data.Elements)
863 | } else if packet.CodecID == Codec16 {
864 | n, err = calculateElementsSizeCodec16(data.Elements)
865 | }
866 | if err != nil {
867 | return nil, err
868 | }
869 | dataSize += n
870 | }
871 | }
872 |
873 | buf := make([]byte, dataSize+12)
874 | binary.BigEndian.PutUint32(buf, 0)
875 | pos := 4
876 |
877 | binary.BigEndian.PutUint32(buf[pos:], uint32(dataSize))
878 | pos += 4
879 |
880 | n, err := encodePacket(packet, buf[pos:])
881 | if err != nil {
882 | return nil, err
883 | }
884 | pos += n
885 |
886 | crc := Crc16IBM(buf[8:pos])
887 | binary.BigEndian.PutUint32(buf[pos:], uint32(crc))
888 |
889 | return buf, nil
890 | }
891 |
892 | func encodeUDPInternal(imei string, packetId uint16, avlPacketId uint8, packet *Packet) ([]byte, error) {
893 | if !isCodecSupported(uint8(packet.CodecID)) {
894 | return nil, fmt.Errorf("codec %d is not supported", packet.CodecID)
895 | }
896 | if packet.Messages != nil && packet.Data != nil {
897 | return nil, fmt.Errorf("invalid packet. Only one of packet.Message, packet.Data should contain data")
898 | }
899 | if isCMDCodecId(uint8(packet.CodecID)) && packet.Messages == nil {
900 | return nil, fmt.Errorf("nothing to encode, packet.Messages is nil")
901 | }
902 | if !isCMDCodecId(uint8(packet.CodecID)) && packet.Data == nil {
903 | return nil, fmt.Errorf("nothing to encode, packet.Data is nil")
904 | }
905 |
906 | dataSize := 11 + len(imei) // packet fields(3) + header(5) + imeiLen(2) + avlPacketId(1) + len(imei)
907 |
908 | if isCMDCodecId(uint8(packet.CodecID)) {
909 | for _, command := range packet.Messages {
910 | dataSize += len(command.Text) + 5
911 | if packet.CodecID == Codec14 {
912 | dataSize += 8
913 | } else if packet.CodecID == Codec13 {
914 | dataSize += 4
915 | } else if packet.CodecID == Codec15 {
916 | dataSize += 12
917 | }
918 | }
919 | } else {
920 | var n int
921 | var err error
922 | for _, data := range packet.Data {
923 | dataSize += 24 // data fields size
924 | if packet.CodecID == Codec8 {
925 | n, err = calculateElementsSizeCodec8(data.Elements)
926 | } else if packet.CodecID == Codec8E {
927 | n, err = calculateElementsSizeCodec8E(data.Elements)
928 | } else if packet.CodecID == Codec16 {
929 | n, err = calculateElementsSizeCodec16(data.Elements)
930 | }
931 | if err != nil {
932 | return nil, err
933 | }
934 | dataSize += n
935 | }
936 | }
937 |
938 | // Maximum AVL packet size is 1280 bytes.
939 | if dataSize > 1280 {
940 | return nil, fmt.Errorf("maximum AVL packet size is 1280 bytes. Estimated size is equal to %v bytes", dataSize)
941 | }
942 |
943 | buf := make([]byte, dataSize)
944 | binary.BigEndian.PutUint16(buf, uint16(dataSize-2))
945 | binary.BigEndian.PutUint16(buf[2:], packetId)
946 | buf[4] = 0x01
947 | buf[5] = avlPacketId
948 | binary.BigEndian.PutUint16(buf[6:], uint16(len(imei)))
949 | copy(buf[8:], imei)
950 | pos := 8 + len(imei)
951 | _, err := encodePacket(packet, buf[pos:])
952 | if err != nil {
953 | return nil, err
954 | }
955 | return buf, nil
956 | }
957 |
958 | func encodePacket(packet *Packet, buf []byte) (int, error) {
959 | if !isCodecSupported(uint8(packet.CodecID)) {
960 | return 0, fmt.Errorf("codec %d is not supported", packet.CodecID)
961 | }
962 |
963 | if len(buf) < 3 {
964 | return 0, fmt.Errorf("output buffer too small, expected at least 3 bytes for output buffer, got %d", len(buf))
965 | }
966 | buf[0] = uint8(packet.CodecID)
967 |
968 | pos := 2
969 | if isCMDCodecId(uint8(packet.CodecID)) {
970 | if len(packet.Messages) > 255 {
971 | return 0, fmt.Errorf("too many messages - %d (> 255)", len(packet.Messages))
972 | }
973 | buf[1] = uint8(len(packet.Messages))
974 | for _, message := range packet.Messages {
975 | n, err := encodeMessage(packet.CodecID, &message, buf[pos:])
976 | if err != nil {
977 | return 0, err
978 | }
979 | pos += n
980 | }
981 | if pos >= len(buf) {
982 | return 0, fmt.Errorf("output buffer is too small, expected at least %d bytes for output buffer, got %d", pos+1, len(buf))
983 | }
984 | buf[pos] = uint8(len(packet.Messages))
985 | pos++
986 | } else {
987 | if len(packet.Data) > 255 {
988 | return 0, fmt.Errorf("too many data frames - %d (> 255)", len(packet.Data))
989 | }
990 | buf[1] = uint8(len(packet.Data))
991 | for _, data := range packet.Data {
992 | n, err := encodeData(packet.CodecID, &data, buf[pos:])
993 | if err != nil {
994 | return 0, err
995 | }
996 | pos += n
997 | }
998 | if pos >= len(buf) {
999 | return 0, fmt.Errorf("output buffer is too small, expected at least %d bytes for output buffer, got %d", pos+1, len(buf))
1000 | }
1001 | buf[pos] = uint8(len(packet.Data))
1002 | pos++
1003 | }
1004 |
1005 | return pos, nil
1006 | }
1007 |
1008 | func encodeMessage(codecId CodecId, message *Message, buf []byte) (int, error) {
1009 | if codecId == Codec12 && message.Type != TypeResponse && message.Type != TypeCommand ||
1010 | codecId == Codec13 && message.Type != TypeResponse ||
1011 | codecId == Codec14 && message.Type != TypeResponse && message.Type != TypeCommand && message.Type != TypeNotExecuted {
1012 | return 0, fmt.Errorf("message type 0x%X is not supported with codec %d", message.Type, codecId)
1013 | }
1014 |
1015 | size := len(message.Text)
1016 | if codecId == Codec14 {
1017 | size += 8
1018 | } else if codecId == Codec13 {
1019 | size += 4
1020 | } else if codecId == Codec15 {
1021 | size += 12
1022 | }
1023 |
1024 | if len(buf) < size+5 {
1025 | return 0, fmt.Errorf("output buffer is too small, expected at least %d bytes for output buffer, got %d", size+5, len(buf))
1026 | }
1027 |
1028 | buf[0] = uint8(message.Type)
1029 | binary.BigEndian.PutUint32(buf[1:], uint32(size))
1030 | pos := 5
1031 |
1032 | if codecId == Codec13 || codecId == Codec15 {
1033 | binary.BigEndian.PutUint32(buf[pos:], message.Timestamp)
1034 | pos += 4
1035 | }
1036 | if codecId == Codec14 || codecId == Codec15 {
1037 | var imei []byte
1038 | var err error
1039 | if (len(message.Imei) % 2) != 0 {
1040 | imei, err = hex.DecodeString("0" + message.Imei)
1041 | } else {
1042 | imei, err = hex.DecodeString(message.Imei)
1043 | }
1044 |
1045 | if err != nil {
1046 | return 0, err
1047 | }
1048 |
1049 | copy(buf[pos:], imei[:8])
1050 | pos += 8
1051 | }
1052 |
1053 | copy(buf[pos:], message.Text)
1054 | pos += len(message.Text)
1055 |
1056 | return pos, nil
1057 | }
1058 |
1059 | func encodeData(codecId CodecId, data *Data, buf []byte) (int, error) {
1060 | if len(buf) < 24 {
1061 | return 0, fmt.Errorf("output buffer is too small, expected at least 24 bytes for output buffer, got %d", len(buf))
1062 | }
1063 |
1064 | binary.BigEndian.PutUint64(buf, data.TimestampMs)
1065 | buf[8] = data.Priority
1066 | binary.BigEndian.PutUint32(buf[9:], uint32(data.Lng*10000000.0))
1067 | binary.BigEndian.PutUint32(buf[13:], uint32(data.Lat*10000000.0))
1068 | binary.BigEndian.PutUint16(buf[17:], uint16(data.Altitude))
1069 | binary.BigEndian.PutUint16(buf[19:], data.Angle)
1070 | buf[21] = data.Satellites
1071 | binary.BigEndian.PutUint16(buf[22:], data.Speed)
1072 |
1073 | var n int
1074 | var err error
1075 | if codecId == Codec8 {
1076 | n, err = encodeElementsCodec8(data, buf[24:])
1077 | } else if codecId == Codec8E {
1078 | n, err = encodeElementsCodec8E(data, buf[24:])
1079 | } else if codecId == Codec16 {
1080 | n, err = encodeElementsCodec16(data, buf[24:])
1081 | } else {
1082 | return 0, fmt.Errorf("unsupported codec %d", codecId)
1083 | }
1084 |
1085 | if err != nil {
1086 | return 0, err
1087 | }
1088 |
1089 | return n + 24, err
1090 | }
1091 |
1092 | func encodeElementsCodec8(data *Data, buf []byte) (int, error) {
1093 | if data.EventID > 255 {
1094 | return 0, fmt.Errorf("event id (%d) is too large (> 255)", data.EventID)
1095 | }
1096 | ioCount := len(data.Elements)
1097 | if ioCount > 255 {
1098 | return 0, fmt.Errorf("too many i/o elements - %d (> 255)", ioCount)
1099 | }
1100 | expectedSize, err := calculateElementsSizeCodec8(data.Elements)
1101 | if err != nil {
1102 | return 0, err
1103 | }
1104 |
1105 | if len(buf) < expectedSize {
1106 | return 0, fmt.Errorf("output buffer is too small, expected %d bytes for output buffer, got %d", expectedSize, len(buf))
1107 | }
1108 |
1109 | buf[0] = uint8(data.EventID)
1110 | buf[1] = uint8(ioCount)
1111 | pos := 2
1112 | for i := 1; i <= 8; i *= 2 {
1113 | written := 0
1114 | dataCountPos := pos
1115 | pos++
1116 | for j := 0; j < ioCount; j++ {
1117 | length := len(data.Elements[j].Value)
1118 | if length == i {
1119 | buf[pos] = uint8(data.Elements[j].Id)
1120 | pos++
1121 | copy(buf[pos:], data.Elements[j].Value)
1122 | pos += length
1123 | written++
1124 | }
1125 | }
1126 | buf[dataCountPos] = uint8(written) // written <= 255
1127 | }
1128 |
1129 | return pos, nil
1130 | }
1131 |
1132 | func calculateElementsSizeCodec8(elements []IOElement) (int, error) {
1133 | expectedSize := 6 // eventId, ioCount (2) + 4 for each group uint8 size
1134 | for i := 0; i < len(elements); i++ {
1135 | if elements[i].Id > 255 {
1136 | return 0, fmt.Errorf("IOElement[%d].Id (%d) is too large (> 255)", i, elements[i].Id)
1137 | }
1138 | length := len(elements[i].Value)
1139 | if length != 1 && length != 2 && length != 4 && length != 8 {
1140 | return 0, fmt.Errorf("IOElement[%d].Value has invalid size %d (allowed: 1,2,4,8)", i, length)
1141 | }
1142 | expectedSize += length + 1
1143 | }
1144 | return expectedSize, nil
1145 | }
1146 |
1147 | func encodeElementsCodec16(data *Data, buf []byte) (int, error) {
1148 | ioCount := len(data.Elements)
1149 | if ioCount > 255 {
1150 | return 0, fmt.Errorf("too many i/o elements - %d (> 255)", ioCount)
1151 | }
1152 |
1153 | if data.GenerationType > 7 {
1154 | return 0, fmt.Errorf("invalid generation type, must be number from 0 to 7, got %v", data.GenerationType)
1155 | }
1156 |
1157 | expectedSize, err := calculateElementsSizeCodec16(data.Elements)
1158 | if err != nil {
1159 | return 0, err
1160 | }
1161 |
1162 | if len(buf) < expectedSize {
1163 | return 0, fmt.Errorf("output buffer is too small, expected %d bytes for output buffer, got %d", expectedSize, len(buf))
1164 | }
1165 |
1166 | binary.BigEndian.PutUint16(buf, data.EventID)
1167 | buf[2] = uint8(data.GenerationType)
1168 | buf[3] = uint8(ioCount)
1169 |
1170 | pos := 4
1171 | for i := 1; i <= 8; i *= 2 {
1172 | written := 0
1173 | dataCountPos := pos
1174 | pos++
1175 | for j := 0; j < ioCount; j++ {
1176 | length := len(data.Elements[j].Value)
1177 | if length == i {
1178 | binary.BigEndian.PutUint16(buf[pos:], data.Elements[j].Id)
1179 | pos += 2
1180 | copy(buf[pos:], data.Elements[j].Value)
1181 | pos += length
1182 | written++
1183 | }
1184 | }
1185 | buf[dataCountPos] = uint8(written) // written <= 255
1186 | }
1187 |
1188 | return pos, nil
1189 | }
1190 |
1191 | func calculateElementsSizeCodec16(elements []IOElement) (int, error) {
1192 | expectedSize := 8 // eventId, ioCount, generationType (4) + 4 for each group uint8 size
1193 | for i := 0; i < len(elements); i++ {
1194 | length := len(elements[i].Value)
1195 | if length != 1 && length != 2 && length != 4 && length != 8 {
1196 | return 0, fmt.Errorf("IOElement[%d].Value has invalid size %d (allowed: 1,2,4,8)", i, length)
1197 | }
1198 | expectedSize += length + 2
1199 | }
1200 | return expectedSize, nil
1201 | }
1202 |
1203 | func encodeElementsCodec8E(data *Data, buf []byte) (int, error) {
1204 | ioCount := len(data.Elements)
1205 | if ioCount > 65535 {
1206 | return 0, fmt.Errorf("too many i/o elements - %d (> 65535)", ioCount)
1207 | }
1208 |
1209 | expectedSize, err := calculateElementsSizeCodec8E(data.Elements)
1210 | if err != nil {
1211 | return 0, err
1212 | }
1213 |
1214 | if len(buf) < expectedSize {
1215 | return 0, fmt.Errorf("output buffer is too small, expected %d bytes for output buffer, got %d", expectedSize, len(buf))
1216 | }
1217 |
1218 | binary.BigEndian.PutUint16(buf, data.EventID)
1219 | binary.BigEndian.PutUint16(buf[2:], uint16(ioCount))
1220 |
1221 | pos := 4
1222 | for i := 1; i <= 8; i *= 2 {
1223 | written := 0
1224 | dataCountPos := pos
1225 | pos += 2
1226 | for j := 0; j < ioCount; j++ {
1227 | length := len(data.Elements[j].Value)
1228 | if length == i {
1229 | binary.BigEndian.PutUint16(buf[pos:], data.Elements[j].Id)
1230 | pos += 2
1231 | copy(buf[pos:], data.Elements[j].Value)
1232 | pos += length
1233 | written++
1234 | }
1235 | }
1236 | binary.BigEndian.PutUint16(buf[dataCountPos:], uint16(written)) // written <= 65535
1237 | }
1238 |
1239 | ioCountNX := 0
1240 | ioCountNXPos := pos
1241 | pos += 2
1242 | for j := 0; j < ioCount; j++ {
1243 | length := len(data.Elements[j].Value)
1244 | if length != 1 && length != 2 && length != 4 && length != 8 {
1245 | binary.BigEndian.PutUint16(buf[pos:], data.Elements[j].Id)
1246 | pos += 2
1247 | binary.BigEndian.PutUint16(buf[pos:], uint16(length))
1248 | pos += 2
1249 | copy(buf[pos:], data.Elements[j].Value)
1250 | pos += length
1251 |
1252 | ioCountNX++
1253 | }
1254 | }
1255 | binary.BigEndian.PutUint16(buf[ioCountNXPos:], uint16(ioCountNX))
1256 |
1257 | return pos, nil
1258 | }
1259 |
1260 | func calculateElementsSizeCodec8E(elements []IOElement) (int, error) {
1261 | expectedSize := 14 // eventId, ioCount, ioCountNX (6) + 8 for each group uint8 size
1262 | for i := 0; i < len(elements); i++ {
1263 | length := len(elements[i].Value)
1264 | if length > 65535 {
1265 | return 0, fmt.Errorf("IOElement[%d].Value length %d too high (> 65535)", i, length)
1266 | }
1267 | if length == 0 {
1268 | return 0, fmt.Errorf("IOElement[%d].Value length is 0", i)
1269 | }
1270 |
1271 | if length != 1 && length != 2 && length != 4 && length != 8 {
1272 | expectedSize += length + 4
1273 | } else {
1274 | expectedSize += length + 2
1275 | }
1276 | }
1277 | return expectedSize, nil
1278 | }
1279 |
1280 | func isCodecSupported(id uint8) bool {
1281 | return id == uint8(Codec8) || id == uint8(Codec8E) || id == uint8(Codec16) ||
1282 | id == uint8(Codec12) || id == uint8(Codec13) || id == uint8(Codec14) || id == uint8(Codec15)
1283 | }
1284 |
1285 | func isCMDCodecId(id uint8) bool {
1286 | return id == uint8(Codec12) || id == uint8(Codec13) || id == uint8(Codec14) || id == uint8(Codec15)
1287 | }
1288 |
1289 | func readFromReader(input io.Reader, buffer []byte) error {
1290 | size := len(buffer)
1291 | read := 0
1292 | for read < size {
1293 | n, err := input.Read(buffer[read:])
1294 | if n == 0 || errors.Is(err, io.EOF) {
1295 | return fmt.Errorf("unable to read packet. received %v of %v bytes", read, size)
1296 | }
1297 | if err != nil {
1298 | return err
1299 | }
1300 | read += n
1301 | }
1302 | return nil
1303 | }
1304 |
--------------------------------------------------------------------------------