├── 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 | --------------------------------------------------------------------------------