├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bits.go ├── bits_test.go ├── bptc ├── bptc.go ├── bptc_test.go └── testdata │ └── bptc.go ├── controlblock.go ├── controlblock_test.go ├── crc.go ├── crc ├── crc16 │ └── ctc16.go └── quadres_16_7 │ └── quadres_16_7.go ├── crc_test.go ├── data.go ├── data_encoding.go ├── data_test.go ├── dataheader.go ├── dataheader_test.go ├── dmr.go ├── docs ├── DMRplus IPSC Protocol for HB repeater (20150726).pdf ├── MMDVM Specification 20151208.pdf └── ts_10236101v010405p.pdf ├── fec ├── fec.go ├── golay_20_8.go ├── golay_23_12.go ├── hamming_15_13_3.go └── rs_12_9.go ├── homebrew ├── config.go ├── homebrew.go ├── peer.go └── repeaterconfiguration.go ├── ipsc ├── ipsc.go ├── mask.go ├── message.go ├── packet.go └── payload.go ├── lc ├── gpsinfo.go ├── lc.go ├── serviceoptions │ └── serviceoptions.go ├── talkeralias.go └── voicechanneluser.go ├── packet.go ├── repeater.go ├── sync.go ├── terminal └── terminal.go ├── trellis └── trellis.go ├── vbptc └── vbptc.go ├── version.go └── voice.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pdf filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # Editor remains 27 | *.swp 28 | 29 | # Dump files 30 | *.dump 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | # Debian/Ubuntu ships with older Go 5 | - 1.3.3 6 | # Recent stable Go 7 | - 1.5.2 8 | 9 | install: 10 | - go get -d -v ./... 11 | 12 | script: 13 | - go test -v ./... 14 | - go test -coverprofile=cover.out && go tool cover -html=cover.out -o coverage.html || true 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 pd0mz 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-dmr 2 | 3 | Golang Digital Mobile Radio protocols. 4 | 5 | [![Build Status](https://travis-ci.com/pd0mz/go-dmr.svg?token=24oDsRPafHpYb3stEpVa&branch=master)](https://travis-ci.com/pd0mz/go-dmr) 6 | 7 | ## References 8 | 9 | The DMR Air Interface protocol is specified in *Electromagnetic compatibility 10 | and Radio spectrum Matters (ERM); Digital Mobile Radio (DMR) Systems; Part 1: 11 | DMR Air Interface (AI) protocol*, [ETSI TS 102 361-1][ETSI TS 102 361-1]. 12 | 13 | The Brandmeister Homebrew protocol is specified in 14 | [IPSC Protocol Specs for homebrew DMR repeater][homebrew specs] 15 | by [Hans DL5DI](mailto:dl5di@gmx.de), 16 | [Jonathan Naylor (G4KLXG)](https://twitter.com/g4klx) and Torsten Schultze 17 | (DG1HT). 18 | 19 | [ETSI TS 102 361-1]: docs/ts_10236101v010405p.pdf 20 | [homebrew specs]: docs/DMRplus%20IPSC%20Protocol%20for%20HB%20repeater%20(20150726).pdf 21 | 22 | ## Warning 23 | 24 | This implementation is not suitable for commercial use and is for educational 25 | purposes only. 26 | 27 | ## Acknowledgements 28 | 29 | The implementation is possible because of the invaluable help from the 30 | following persons. Thanks for your patience and providing me with sample data 31 | and links to test the protocols. 32 | 33 | * Rudy Hardeman (PD0ZRY) 34 | * Artem Prilutskiy (R3ABM) 35 | -------------------------------------------------------------------------------- /bits.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | // Various sizes of information chunks. 4 | const ( 5 | PayloadBits = 98 + 10 + 48 + 10 + 98 6 | InfoHalfBits = 98 7 | InfoBits = 2 * InfoHalfBits 8 | InfoSize = 12 // After BPTC(196, 96) decoding 9 | SlotTypeHalfBits = 10 10 | SlotTypeBits = 2 * SlotTypeHalfBits 11 | SignalBits = 48 12 | SyncOffsetBits = InfoHalfBits + SlotTypeHalfBits 13 | SyncBits = SignalBits 14 | VoiceHalfBits = 108 15 | VoiceBits = 2 * VoiceHalfBits 16 | EMBHalfBits = 8 17 | EMBBits = 2 * EMBHalfBits 18 | EMBSignallingLCFragmentBits = 32 19 | ) 20 | 21 | // Because Go doesn't have binary literals ("We've found hex and octal to be sufficient") 22 | const ( 23 | B00000000 = iota 24 | B00000001 25 | B00000010 26 | B00000011 27 | B00000100 28 | B00000101 29 | B00000110 30 | B00000111 31 | B00001000 32 | B00001001 33 | B00001010 34 | B00001011 35 | B00001100 36 | B00001101 37 | B00001110 38 | B00001111 39 | B00010000 40 | B00010001 41 | B00010010 42 | B00010011 43 | B00010100 44 | B00010101 45 | B00010110 46 | B00010111 47 | B00011000 48 | B00011001 49 | B00011010 50 | B00011011 51 | B00011100 52 | B00011101 53 | B00011110 54 | B00011111 55 | B00100000 56 | B00100001 57 | B00100010 58 | B00100011 59 | B00100100 60 | B00100101 61 | B00100110 62 | B00100111 63 | B00101000 64 | B00101001 65 | B00101010 66 | B00101011 67 | B00101100 68 | B00101101 69 | B00101110 70 | B00101111 71 | B00110000 72 | B00110001 73 | B00110010 74 | B00110011 75 | B00110100 76 | B00110101 77 | B00110110 78 | B00110111 79 | B00111000 80 | B00111001 81 | B00111010 82 | B00111011 83 | B00111100 84 | B00111101 85 | B00111110 86 | B00111111 87 | B01000000 88 | B01000001 89 | B01000010 90 | B01000011 91 | B01000100 92 | B01000101 93 | B01000110 94 | B01000111 95 | B01001000 96 | B01001001 97 | B01001010 98 | B01001011 99 | B01001100 100 | B01001101 101 | B01001110 102 | B01001111 103 | B01010000 104 | B01010001 105 | B01010010 106 | B01010011 107 | B01010100 108 | B01010101 109 | B01010110 110 | B01010111 111 | B01011000 112 | B01011001 113 | B01011010 114 | B01011011 115 | B01011100 116 | B01011101 117 | B01011110 118 | B01011111 119 | B01100000 120 | B01100001 121 | B01100010 122 | B01100011 123 | B01100100 124 | B01100101 125 | B01100110 126 | B01100111 127 | B01101000 128 | B01101001 129 | B01101010 130 | B01101011 131 | B01101100 132 | B01101101 133 | B01101110 134 | B01101111 135 | B01110000 136 | B01110001 137 | B01110010 138 | B01110011 139 | B01110100 140 | B01110101 141 | B01110110 142 | B01110111 143 | B01111000 144 | B01111001 145 | B01111010 146 | B01111011 147 | B01111100 148 | B01111101 149 | B01111110 150 | B01111111 151 | B10000000 152 | B10000001 153 | B10000010 154 | B10000011 155 | B10000100 156 | B10000101 157 | B10000110 158 | B10000111 159 | B10001000 160 | B10001001 161 | B10001010 162 | B10001011 163 | B10001100 164 | B10001101 165 | B10001110 166 | B10001111 167 | B10010000 168 | B10010001 169 | B10010010 170 | B10010011 171 | B10010100 172 | B10010101 173 | B10010110 174 | B10010111 175 | B10011000 176 | B10011001 177 | B10011010 178 | B10011011 179 | B10011100 180 | B10011101 181 | B10011110 182 | B10011111 183 | B10100000 184 | B10100001 185 | B10100010 186 | B10100011 187 | B10100100 188 | B10100101 189 | B10100110 190 | B10100111 191 | B10101000 192 | B10101001 193 | B10101010 194 | B10101011 195 | B10101100 196 | B10101101 197 | B10101110 198 | B10101111 199 | B10110000 200 | B10110001 201 | B10110010 202 | B10110011 203 | B10110100 204 | B10110101 205 | B10110110 206 | B10110111 207 | B10111000 208 | B10111001 209 | B10111010 210 | B10111011 211 | B10111100 212 | B10111101 213 | B10111110 214 | B10111111 215 | B11000000 216 | B11000001 217 | B11000010 218 | B11000011 219 | B11000100 220 | B11000101 221 | B11000110 222 | B11000111 223 | B11001000 224 | B11001001 225 | B11001010 226 | B11001011 227 | B11001100 228 | B11001101 229 | B11001110 230 | B11001111 231 | B11010000 232 | B11010001 233 | B11010010 234 | B11010011 235 | B11010100 236 | B11010101 237 | B11010110 238 | B11010111 239 | B11011000 240 | B11011001 241 | B11011010 242 | B11011011 243 | B11011100 244 | B11011101 245 | B11011110 246 | B11011111 247 | B11100000 248 | B11100001 249 | B11100010 250 | B11100011 251 | B11100100 252 | B11100101 253 | B11100110 254 | B11100111 255 | B11101000 256 | B11101001 257 | B11101010 258 | B11101011 259 | B11101100 260 | B11101101 261 | B11101110 262 | B11101111 263 | B11110000 264 | B11110001 265 | B11110010 266 | B11110011 267 | B11110100 268 | B11110101 269 | B11110110 270 | B11110111 271 | B11111000 272 | B11111001 273 | B11111010 274 | B11111011 275 | B11111100 276 | B11111101 277 | B11111110 278 | B11111111 279 | ) 280 | 281 | // BytesToBits converts a byte slice to a byte slice representing the individual data bits. 282 | func BytesToBits(data []byte) []byte { 283 | var bits = make([]byte, len(data)*8) 284 | for i := 0; i < len(data); i++ { 285 | copy(bits[i*8:], toBits(data[i])) 286 | } 287 | return bits 288 | } 289 | 290 | func toBits(b byte) []byte { 291 | var o = make([]byte, 8) 292 | for bit, mask := 0, byte(128); bit < 8; bit, mask = bit+1, mask>>1 { 293 | if b&mask != 0 { 294 | o[bit] = 1 295 | } 296 | } 297 | return o 298 | } 299 | 300 | // BitsToBytes converts a byte slice of bits to a byte slice. 301 | func BitsToBytes(bits []byte) []byte { 302 | var data = make([]byte, (len(bits)+7)/8) 303 | for i, b := range bits { 304 | if b == 0x01 { 305 | data[i/8] |= (1 << byte(7-(i%8))) 306 | } 307 | } 308 | return data 309 | } 310 | -------------------------------------------------------------------------------- /bits_test.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestBit(t *testing.T) { 9 | var tests = []struct { 10 | Test []byte 11 | Want []byte 12 | }{ 13 | { 14 | []byte{0x2a}, 15 | []byte{0, 0, 1, 0, 1, 0, 1, 0}, 16 | }, 17 | { 18 | []byte{0xbe, 0xef}, 19 | []byte{1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1}, 20 | }, 21 | } 22 | 23 | for _, test := range tests { 24 | got := BytesToBits(test.Test) 25 | if len(got) != len(test.Want) { 26 | t.Fatalf("expected length %d, got %d [%s]", len(test.Want), len(got), string(got)) 27 | } 28 | for i, b := range got { 29 | if b != test.Want[i] { 30 | t.Fatalf("bit %d is off: %v != %v", i, got, test.Want) 31 | } 32 | } 33 | 34 | rev := BitsToBytes(got) 35 | if !bytes.Equal(rev, test.Test) { 36 | t.Fatalf("reverse bits to bytes failed, %v != %v", rev, test.Test) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /bptc/bptc.go: -------------------------------------------------------------------------------- 1 | package bptc 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/pd0mz/go-dmr" 8 | ) 9 | 10 | var ( 11 | debug bool 12 | 13 | // deinterleave matrix 14 | dm = [256]uint8{} 15 | ) 16 | 17 | func init() { 18 | debug = os.Getenv("DEBUG_DMR_BPTC") != "" 19 | 20 | var i uint32 21 | for i = 0; i < 0x100; i++ { 22 | dm[i] = uint8((i * 181) % 196) 23 | } 24 | } 25 | 26 | func dump(bits []byte) { 27 | for row := 0; row < 13; row++ { 28 | if row == 0 { 29 | fmt.Printf("col # ") 30 | for col := 0; col < 15; col++ { 31 | fmt.Printf("%02d ", col+1) 32 | if col == 10 { 33 | fmt.Print("| ") 34 | } 35 | } 36 | fmt.Println("") 37 | } 38 | if row == 9 { 39 | fmt.Println(" ------------------------------- ------------") 40 | } 41 | for col := 0; col < 15; col++ { 42 | if col == 0 { 43 | fmt.Printf("row #%02d: ", row+1) 44 | } 45 | fmt.Printf(" %d ", bits[col+row*15+1]) 46 | if col == 10 { 47 | fmt.Print("| ") 48 | } 49 | } 50 | fmt.Println("") 51 | } 52 | } 53 | 54 | func Decode(info, data []byte) error { 55 | if len(info) < 196 { 56 | return fmt.Errorf("bptc: info size %d too small, need at least 196 bits", len(info)) 57 | } 58 | if len(data) < 12 { 59 | return fmt.Errorf("bptc: data size %d too small, need at least 12 bytes", len(data)) 60 | } 61 | 62 | var ( 63 | i, j, k uint32 64 | bits = make([]byte, 196) 65 | temp = make([]byte, 196) 66 | ) 67 | 68 | // Deinterleave 69 | for i = 1; i < 197; i++ { 70 | bits[i-1] = info[dm[i]] 71 | } 72 | 73 | if debug { 74 | dump(bits) 75 | } 76 | 77 | // Hamming checks 78 | if err := hamming_check(bits); err != nil { 79 | //return err 80 | //log.Warningf("hamming check failed: %v", err) 81 | } 82 | 83 | // Extract data bits 84 | for i, k = 3, 0; i < 11; i, k = i+1, k+1 { 85 | temp[k] = bits[0*15+i] 86 | } 87 | for j = 1; j < 9; j++ { 88 | for i = 0; i < 11; i, k = i+1, k+1 { 89 | temp[k] = bits[j*15+i] 90 | } 91 | } 92 | 93 | copy(data, dmr.BitsToBytes(temp)) 94 | return nil 95 | } 96 | 97 | func Encode(data, info []byte) error { 98 | if len(data) < 12 { 99 | return fmt.Errorf("bptc: data size %d too small, need at least 12 bytes", len(data)) 100 | } 101 | if len(info) < 196 { 102 | return fmt.Errorf("bptc: info size %d too small, need at least 196 bits", len(info)) 103 | } 104 | 105 | var ( 106 | bits = dmr.BytesToBits(data) 107 | temp = make([]byte, 196) 108 | errs = make([]byte, 4) 109 | cols = make([]byte, 13) 110 | ) 111 | 112 | var c, r, k uint32 113 | for r = 0; r < 9; r++ { 114 | if r == 0 { 115 | for c = 3; c < 11; c, k = c+1, k+1 { 116 | temp[c] = bits[k] 117 | } 118 | } else { 119 | for c = 0; c < 11; c, k = c+1, k+1 { 120 | temp[c+r*15] = bits[k] 121 | } 122 | } 123 | 124 | hamming_15_11_3_parity(temp[r*15:], errs) 125 | temp[r*15+11] = errs[0] 126 | temp[r*15+12] = errs[1] 127 | temp[r*15+13] = errs[2] 128 | temp[r*15+14] = errs[3] 129 | } 130 | for c = 0; c < 15; c++ { 131 | for r = 0; r < 9; r++ { 132 | cols[r] = temp[c+r*15+1] 133 | } 134 | 135 | hamming_13_9_3_parity(cols, errs) 136 | temp[c+135+1] = errs[0] 137 | temp[c+135+15+1] = errs[1] 138 | temp[c+135+30+1] = errs[2] 139 | temp[c+135+45+1] = errs[3] 140 | } 141 | 142 | if debug { 143 | dump(temp) 144 | } 145 | 146 | // Interleave 147 | for k = 1; k < 197; k++ { 148 | info[dm[k]] = temp[k-1] 149 | } 150 | 151 | return nil 152 | } 153 | 154 | // Hamming(13, 9, 3) check 155 | func hamming_13_9_3_parity(bits, errs []byte) bool { 156 | errs[0] = bits[0] ^ bits[1] ^ bits[3] ^ bits[5] ^ bits[6] 157 | errs[1] = bits[0] ^ bits[1] ^ bits[2] ^ bits[4] ^ bits[6] ^ bits[7] 158 | errs[2] = bits[0] ^ bits[1] ^ bits[2] ^ bits[3] ^ bits[5] ^ bits[7] ^ bits[8] 159 | errs[3] = bits[0] ^ bits[2] ^ bits[4] ^ bits[5] ^ bits[8] 160 | return (errs[0] == bits[9]) && (errs[1] == bits[10]) && (errs[2] == bits[11]) && (errs[3] == bits[12]) 161 | } 162 | 163 | // Hamming(15, 11, 3) check 164 | func hamming_15_11_3_parity(bits, errs []byte) bool { 165 | errs[0] = bits[0] ^ bits[1] ^ bits[2] ^ bits[3] ^ bits[5] ^ bits[7] ^ bits[8] 166 | errs[1] = bits[1] ^ bits[2] ^ bits[3] ^ bits[4] ^ bits[6] ^ bits[8] ^ bits[9] 167 | errs[2] = bits[2] ^ bits[3] ^ bits[4] ^ bits[5] ^ bits[7] ^ bits[9] ^ bits[10] 168 | errs[3] = bits[0] ^ bits[1] ^ bits[2] ^ bits[4] ^ bits[6] ^ bits[7] ^ bits[10] 169 | return (errs[0] == bits[11]) && (errs[1] == bits[12]) && (errs[2] == bits[13]) && (errs[3] == bits[14]) 170 | } 171 | 172 | // hamming_check checks each row with a Hamming(15,11,3) code and each column with Hamming(13, 9, 3) 173 | func hamming_check(bits []byte) error { 174 | var ( 175 | c, r, k uint32 176 | row = make([]byte, 15) 177 | col = make([]byte, 13) 178 | errs = make([]byte, 4) 179 | ) 180 | 181 | // Run through each of the 9 rows containing data 182 | for r = 0; r < 9; r++ { 183 | k = r*15 + 1 184 | for a := 0; a < 15; a++ { 185 | row[a] = bits[k] 186 | } 187 | if !hamming_15_11_3_parity(row, errs) { 188 | return fmt.Errorf("hamming(15, 11, 3) check failed on row #%d", r) 189 | } 190 | } 191 | 192 | // Run through each of the 15 columns 193 | for c = 0; c < 15; c++ { 194 | k = c + 1 195 | for a := 0; a < 13; a, k = a+1, k+15 { 196 | col[a] = bits[k] 197 | } 198 | if !hamming_13_9_3_parity(col, errs) { 199 | return fmt.Errorf("hamming(13, 9, 3) check failed on col #%d", c) 200 | } 201 | } 202 | 203 | return nil 204 | } 205 | -------------------------------------------------------------------------------- /bptc/bptc_test.go: -------------------------------------------------------------------------------- 1 | package bptc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "testing" 7 | 8 | "github.com/pd0mz/go-dmr" 9 | ) 10 | 11 | var ( 12 | encoded = []byte{ 13 | 0x4b, 0xb2, 0x1d, 0x6d, 0x82, 0xd4, 14 | 0x23, 0x34, 0x0e, 0xe9, 0x66, 0xf3, 15 | 0xc2, 0x20, 0xc3, 0x87, 0xfd, 0x84, 16 | 0x54, 0x12, 0x4d, 0xb2, 0xd1, 0x40, 17 | 0x70, 18 | } 19 | decoded = []byte{ 20 | 0xbd, 0x00, 0x80, 0x03, 0x1f, 0x29, 21 | 0x66, 0x1f, 0x2c, 0xa4, 0x66, 0x7e, 22 | } 23 | ) 24 | 25 | func TestDecode(t *testing.T) { 26 | var want = dmr.BytesToBits(encoded) 27 | var test = make([]byte, 12) 28 | 29 | if err := Decode(want, test); err != nil { 30 | t.Fatalf("decode failed: %v", err) 31 | } 32 | 33 | if !bytes.Equal(test, decoded) { 34 | t.Fatalf("decode failed: not equal") 35 | } 36 | t.Logf("input:\n%s", hex.Dump(encoded)) 37 | t.Logf("decoded:\n%s", hex.Dump(test)) 38 | } 39 | 40 | func TestEncode(t *testing.T) { 41 | var want = make([]byte, 12) 42 | var bits = make([]byte, 196) 43 | copy(want, decoded) 44 | 45 | if err := Encode(want, bits); err != nil { 46 | t.Fatalf("encode failed: %v", err) 47 | } 48 | 49 | test := dmr.BitsToBytes(bits) 50 | if !bytes.Equal(test, encoded) { 51 | t.Fatalf("encode failed: not equal") 52 | } 53 | 54 | t.Logf("input:\n%s", hex.Dump(decoded)) 55 | t.Logf("encoded:\n%s", hex.Dump(test)) 56 | } 57 | -------------------------------------------------------------------------------- /bptc/testdata/bptc.go: -------------------------------------------------------------------------------- 1 | package bptc 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/pd0mz/go-dmr" 8 | "github.com/pd0mz/go-dmr/fec" 9 | ) 10 | 11 | // deinterleave matrix 12 | var dm = [256]uint8{} 13 | var debug bool 14 | 15 | func init() { 16 | var i uint32 17 | for i = 0; i < 0x100; i++ { 18 | dm[i] = uint8((i * 181) % 196) 19 | } 20 | 21 | debug = os.Getenv("DEBUG_DMR_BPTC") != "" 22 | } 23 | 24 | func dump(bits []byte) { 25 | for row := 0; row < 13; row++ { 26 | if row == 0 { 27 | fmt.Printf("col # ") 28 | for col := 0; col < 15; col++ { 29 | fmt.Printf("%02d ", col+1) 30 | if col == 10 { 31 | fmt.Print("| ") 32 | } 33 | } 34 | fmt.Println("") 35 | } 36 | if row == 9 { 37 | fmt.Println(" ------------------------------- ------------") 38 | } 39 | for col := 0; col < 15; col++ { 40 | if col == 0 { 41 | fmt.Printf("row #%02d: ", row+1) 42 | } 43 | fmt.Printf(" %d ", bits[col+row*15+1]) 44 | if col == 10 { 45 | fmt.Print("| ") 46 | } 47 | } 48 | fmt.Println("") 49 | } 50 | } 51 | 52 | func Decode(info, data []byte) error { 53 | if len(info) < 196 { 54 | return fmt.Errorf("bptc: info size %d too small, need at least 196 bits", len(info)) 55 | } 56 | if len(data) < 12 { 57 | return fmt.Errorf("bptc: data size %d too small, need at least 12 bytes", len(data)) 58 | } 59 | 60 | var ( 61 | i, j, k uint32 62 | datafr = make([]byte, 196) 63 | extracted = make([]byte, 96) 64 | ) 65 | 66 | // Deinterleave bits 67 | for i = 1; i < 197; i++ { 68 | datafr[i-1] = info[dm[i]] 69 | } 70 | 71 | if debug { 72 | dump(datafr) 73 | } 74 | 75 | // Zero reserved bits 76 | for i = 0; i < 3; i++ { 77 | datafr[0*15+i] = 0 78 | } 79 | 80 | for i = 0; i < 15; i++ { 81 | var codeword uint32 82 | for j = 0; j < 13; j++ { 83 | codeword <<= 1 84 | codeword |= uint32(datafr[j*15+i]) 85 | } 86 | 87 | fec.Hamming15_11_3_Correct(&codeword) 88 | codeword &= 0x01ff 89 | for j = 0; j < 9; j++ { 90 | datafr[j*15+i] = byte((codeword >> (8 - j)) & 1) 91 | } 92 | } 93 | for j = 0; j < 9; j++ { 94 | var codeword uint32 95 | for i = 0; i < 15; i++ { 96 | codeword <<= 1 97 | codeword |= uint32(datafr[j*15+i]) 98 | } 99 | fec.Hamming15_11_3_Correct(&codeword) 100 | for i = 0; i < 11; i++ { 101 | datafr[j*15+10-i] = byte((codeword >> i) & 1) 102 | } 103 | } 104 | 105 | // Extract data bits 106 | for i, k = 3, 0; i < 11; i, k = i+1, k+1 { 107 | extracted[k] = datafr[0*15+i] 108 | } 109 | for j = 1; j < 9; j++ { 110 | for i = 0; i < 11; i, k = i+1, k+1 { 111 | extracted[k] = datafr[j*15+i] 112 | } 113 | } 114 | 115 | copy(data, dmr.BitsToBytes(extracted)) 116 | 117 | return nil 118 | } 119 | 120 | func Encode(data, info []byte) error { 121 | if len(data) < 12 { 122 | return fmt.Errorf("bptc: data size %d too small, need at least 12 bytes", len(data)) 123 | } 124 | if len(info) < 196 { 125 | return fmt.Errorf("bptc: info size %d too small, need at least 196 bits", len(info)) 126 | } 127 | 128 | var ( 129 | i, j, k uint32 130 | datafr = make([]byte, 196) 131 | extracted = make([]byte, 96) 132 | ) 133 | 134 | copy(extracted, dmr.BytesToBits(data)) 135 | 136 | for i = 0; i < 9; i++ { 137 | if i == 0 { 138 | for j = 3; j < 11; j++ { 139 | datafr[j+1] = extracted[k] 140 | k++ 141 | } 142 | } else { 143 | for j = 0; j < 11; j++ { 144 | datafr[j+i*15+1] = extracted[k] 145 | k++ 146 | } 147 | } 148 | 149 | datafr[i*15+11+1] = 8 150 | datafr[i*15+12+1] = 8 151 | datafr[i*15+13+1] = 8 152 | datafr[i*15+14+1] = 8 153 | } 154 | 155 | // Interleave bits 156 | for i = 1; i < 197; i++ { 157 | info[dm[i]] = datafr[i-1] 158 | } 159 | 160 | if debug { 161 | dump(info) 162 | } 163 | 164 | return nil 165 | } 166 | -------------------------------------------------------------------------------- /controlblock.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // Control Block Opcode 10 | const ( 11 | OutboundActivationOpcode = B00111000 12 | UnitToUnitVoiceServiceRequestOpcode = B00000100 13 | UnitToUnitVoiceServiceAnswerResponseOpcode = B00000101 14 | NegativeAcknowledgeResponseOpcode = B00100100 15 | PreambleOpcode = B00111101 16 | ) 17 | 18 | type ControlBlock struct { 19 | CRC uint16 20 | Last bool 21 | Opcode uint8 22 | SrcID, DstID uint32 23 | Data ControlBlockData 24 | } 25 | 26 | func (cb *ControlBlock) Bytes() ([]byte, error) { 27 | var data = make([]byte, InfoSize) 28 | 29 | if err := cb.Data.Write(data); err != nil { 30 | return nil, err 31 | } 32 | if cb.Last { 33 | data[0] |= B10000000 34 | } 35 | 36 | data[4] = uint8(cb.DstID >> 16) 37 | data[5] = uint8(cb.DstID >> 8) 38 | data[6] = uint8(cb.DstID) 39 | data[7] = uint8(cb.SrcID >> 16) 40 | data[8] = uint8(cb.SrcID >> 8) 41 | data[9] = uint8(cb.SrcID) 42 | 43 | // Calculate CRC16 44 | for i := 0; i < 10; i++ { 45 | crc16(&cb.CRC, data[i]) 46 | } 47 | crc16end(&cb.CRC) 48 | 49 | // Inverting according to the inversion polynomial. 50 | cb.CRC = ^cb.CRC 51 | // Applying CRC mask, see DMR AI spec. page 143. 52 | cb.CRC ^= 0xa5a5 53 | 54 | data[10] = uint8(cb.CRC >> 8) 55 | data[11] = uint8(cb.CRC) 56 | 57 | return data, nil 58 | } 59 | 60 | func (cb *ControlBlock) String() string { 61 | if cb.Data == nil { 62 | return fmt.Sprintf("CSBK, last %t, %d->%d, unknown (opcode %d)", 63 | cb.Last, cb.SrcID, cb.DstID, cb.Opcode) 64 | } 65 | return fmt.Sprintf("CSBK, last %t, %d->%d, %s (opcode %d)", 66 | cb.Last, cb.SrcID, cb.DstID, cb.Data.String(), cb.Opcode) 67 | } 68 | 69 | type ControlBlockData interface { 70 | String() string 71 | Write([]byte) error 72 | Parse([]byte) error 73 | } 74 | 75 | type OutboundActivation struct{} 76 | 77 | func (d *OutboundActivation) String() string { return "outbound activation" } 78 | 79 | func (d *OutboundActivation) Parse(data []byte) error { 80 | if len(data) != InfoSize { 81 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 82 | } 83 | return nil 84 | } 85 | 86 | func (d *OutboundActivation) Write(data []byte) error { 87 | if len(data) != InfoSize { 88 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 89 | } 90 | data[0] |= OutboundActivationOpcode 91 | return nil 92 | } 93 | 94 | type UnitToUnitVoiceServiceRequest struct { 95 | Options uint8 96 | } 97 | 98 | func (d *UnitToUnitVoiceServiceRequest) String() string { 99 | return fmt.Sprintf("unit to unit voice service request, options %d", d.Options) 100 | } 101 | 102 | func (d *UnitToUnitVoiceServiceRequest) Parse(data []byte) error { 103 | if len(data) != InfoSize { 104 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 105 | } 106 | d.Options = data[2] 107 | return nil 108 | } 109 | 110 | func (d *UnitToUnitVoiceServiceRequest) Write(data []byte) error { 111 | if len(data) != InfoSize { 112 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 113 | } 114 | data[0] |= UnitToUnitVoiceServiceRequestOpcode 115 | data[2] = d.Options 116 | return nil 117 | } 118 | 119 | var _ (ControlBlockData) = (*UnitToUnitVoiceServiceRequest)(nil) 120 | 121 | type UnitToUnitVoiceServiceAnswerResponse struct { 122 | Options uint8 123 | Response uint8 124 | } 125 | 126 | func (d *UnitToUnitVoiceServiceAnswerResponse) String() string { 127 | return fmt.Sprintf("unit to unit voice service answer response, options %d, response %d", d.Options, d.Response) 128 | } 129 | 130 | func (d *UnitToUnitVoiceServiceAnswerResponse) Parse(data []byte) error { 131 | if len(data) != InfoSize { 132 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 133 | } 134 | d.Options = data[2] 135 | d.Response = data[3] 136 | return nil 137 | } 138 | 139 | func (d *UnitToUnitVoiceServiceAnswerResponse) Write(data []byte) error { 140 | if len(data) != InfoSize { 141 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 142 | } 143 | data[0] |= UnitToUnitVoiceServiceAnswerResponseOpcode 144 | data[2] = d.Options 145 | data[3] = d.Response 146 | return nil 147 | } 148 | 149 | var _ (ControlBlockData) = (*UnitToUnitVoiceServiceAnswerResponse)(nil) 150 | 151 | type NegativeAcknowledgeResponse struct { 152 | SourceType bool 153 | ServiceType uint8 154 | Reason uint8 155 | } 156 | 157 | func (d *NegativeAcknowledgeResponse) String() string { 158 | return fmt.Sprintf("negative ACK response, source %t, service %d, reason %d", d.SourceType, d.ServiceType, d.Reason) 159 | } 160 | 161 | func (d *NegativeAcknowledgeResponse) Parse(data []byte) error { 162 | if len(data) != InfoSize { 163 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 164 | } 165 | d.SourceType = (data[2] & B01000000) > 0 166 | d.ServiceType = (data[2] & B00011111) 167 | d.Reason = data[3] 168 | return nil 169 | } 170 | 171 | func (d *NegativeAcknowledgeResponse) Write(data []byte) error { 172 | if len(data) != InfoSize { 173 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 174 | } 175 | data[0] |= NegativeAcknowledgeResponseOpcode 176 | data[2] = d.ServiceType 177 | if d.SourceType { 178 | data[2] |= B01000000 179 | } 180 | data[3] = d.Reason 181 | return nil 182 | } 183 | 184 | var _ (ControlBlockData) = (*NegativeAcknowledgeResponse)(nil) 185 | 186 | type Preamble struct { 187 | DataFollows bool 188 | DstIsGroup bool 189 | Blocks uint8 190 | } 191 | 192 | func (d *Preamble) String() string { 193 | var part = []string{"preamble"} 194 | if d.DataFollows { 195 | part = append(part, "data folllows") 196 | } 197 | if d.DstIsGroup { 198 | part = append(part, "group") 199 | } else { 200 | part = append(part, "unit") 201 | } 202 | part = append(part, fmt.Sprintf("%d blocks", d.Blocks)) 203 | return strings.Join(part, ", ") 204 | } 205 | 206 | func (d *Preamble) Parse(data []byte) error { 207 | if len(data) != InfoSize { 208 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 209 | } 210 | d.DataFollows = (data[2] & B10000000) > 0 211 | d.DstIsGroup = (data[2] & B01000000) > 0 212 | d.Blocks = data[3] 213 | return nil 214 | } 215 | 216 | func (d *Preamble) Write(data []byte) error { 217 | if len(data) != InfoSize { 218 | return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 219 | } 220 | data[0] |= PreambleOpcode 221 | if d.DataFollows { 222 | data[2] |= B10000000 223 | } 224 | if d.DstIsGroup { 225 | data[2] |= B01000000 226 | } 227 | data[3] = d.Blocks 228 | return nil 229 | } 230 | 231 | var _ (ControlBlockData) = (*Preamble)(nil) 232 | 233 | func ParseControlBlock(data []byte) (*ControlBlock, error) { 234 | if len(data) != InfoSize { 235 | return nil, fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data)) 236 | } 237 | 238 | // Calculate CRC16 239 | var crc uint16 240 | for i := 0; i < 10; i++ { 241 | crc16(&crc, data[i]) 242 | } 243 | crc16end(&crc) 244 | 245 | // Inverting according to the inversion polynomial 246 | crc = ^crc 247 | // Applying CRC mask, see DMR AI spec. page 143. 248 | crc ^= 0xa5a5 249 | 250 | // Check packet 251 | if data[0]&B01000000 > 0 { 252 | return nil, errors.New("dmr: CSBK protect flag is set") 253 | } 254 | if data[1] != 0 { 255 | return nil, errors.New("dmr: CSBK feature set ID is set") 256 | } 257 | 258 | cb := &ControlBlock{ 259 | CRC: uint16(data[10])<<8 | uint16(data[11]), 260 | Last: (data[0] & B10000000) > 0, 261 | Opcode: (data[0] & B00111111), 262 | DstID: uint32(data[4])<<16 | uint32(data[5])<<8 | uint32(data[6]), 263 | SrcID: uint32(data[7])<<16 | uint32(data[8])<<8 | uint32(data[9]), 264 | } 265 | 266 | if crc != cb.CRC { 267 | return nil, fmt.Errorf("dmr: control block CRC error (%#04x != %#04x)", crc, cb.CRC) 268 | } 269 | 270 | switch cb.Opcode { 271 | case OutboundActivationOpcode: 272 | cb.Data = &OutboundActivation{} 273 | break 274 | case UnitToUnitVoiceServiceRequestOpcode: 275 | cb.Data = &UnitToUnitVoiceServiceRequest{} 276 | break 277 | case UnitToUnitVoiceServiceAnswerResponseOpcode: 278 | cb.Data = &UnitToUnitVoiceServiceAnswerResponse{} 279 | break 280 | case NegativeAcknowledgeResponseOpcode: 281 | cb.Data = &NegativeAcknowledgeResponse{} 282 | break 283 | case PreambleOpcode: 284 | cb.Data = &Preamble{} 285 | break 286 | default: 287 | return nil, fmt.Errorf("dmr: unknown CSBK opcode %02x (%06b)", cb.Opcode, cb.Opcode) 288 | } 289 | 290 | if err := cb.Data.Parse(data); err != nil { 291 | return nil, err 292 | } 293 | 294 | return cb, nil 295 | } 296 | -------------------------------------------------------------------------------- /controlblock_test.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import "testing" 4 | 5 | func testCSBK(want *ControlBlock, t *testing.T) *ControlBlock { 6 | want.SrcID = 2042214 7 | want.DstID = 2043044 8 | 9 | data, err := want.Bytes() 10 | if err != nil { 11 | t.Fatalf("encode failed: %v", err) 12 | } 13 | 14 | test, err := ParseControlBlock(data) 15 | if err != nil { 16 | t.Fatalf("decode failed: %v", err) 17 | } 18 | if test.SrcID != want.SrcID || test.DstID != want.DstID { 19 | t.Fatal("decode failed, ID wrong") 20 | } 21 | 22 | return test 23 | } 24 | 25 | func TestCSBKOutboundActivation(t *testing.T) { 26 | want := &ControlBlock{ 27 | Opcode: OutboundActivationOpcode, 28 | Data: &OutboundActivation{}, 29 | } 30 | test := testCSBK(want, t) 31 | 32 | _, ok := test.Data.(*OutboundActivation) 33 | switch { 34 | case !ok: 35 | t.Fatalf("decode failed: expected UnitToUnitVoiceServiceRequest, got %T", test.Data) 36 | 37 | default: 38 | t.Logf("decode: %s", test.String()) 39 | } 40 | } 41 | 42 | func TestCSBKUnitToUnitVoiceServiceRequest(t *testing.T) { 43 | want := &ControlBlock{ 44 | Opcode: UnitToUnitVoiceServiceRequestOpcode, 45 | Data: &UnitToUnitVoiceServiceRequest{ 46 | Options: 0x2a, 47 | }, 48 | } 49 | test := testCSBK(want, t) 50 | 51 | d, ok := test.Data.(*UnitToUnitVoiceServiceRequest) 52 | switch { 53 | case !ok: 54 | t.Fatalf("decode failed: expected UnitToUnitVoiceServiceRequest, got %T", test.Data) 55 | 56 | case d.Options != 0x2a: 57 | t.Fatalf("decode failed, options wrong") 58 | 59 | default: 60 | t.Logf("decode: %s", test.String()) 61 | } 62 | } 63 | 64 | func TestCSBKUnitToUnitVoiceServiceAnswerResponse(t *testing.T) { 65 | want := &ControlBlock{ 66 | Opcode: UnitToUnitVoiceServiceAnswerResponseOpcode, 67 | Data: &UnitToUnitVoiceServiceAnswerResponse{ 68 | Options: 0x17, 69 | Response: 0x2a, 70 | }, 71 | } 72 | test := testCSBK(want, t) 73 | 74 | d, ok := test.Data.(*UnitToUnitVoiceServiceAnswerResponse) 75 | switch { 76 | case !ok: 77 | t.Fatalf("decode failed: expected UnitToUnitVoiceServiceAnswerResponse, got %T", test.Data) 78 | 79 | case d.Response != 0x2a: 80 | t.Fatalf("decode failed, response wrong") 81 | 82 | case d.Options != 0x17: 83 | t.Fatalf("decode failed, options wrong") 84 | 85 | default: 86 | t.Logf("decode: %s", test.String()) 87 | } 88 | } 89 | 90 | func TestCSBKNegativeAcknowledgeResponse(t *testing.T) { 91 | want := &ControlBlock{ 92 | Opcode: NegativeAcknowledgeResponseOpcode, 93 | Data: &NegativeAcknowledgeResponse{ 94 | ServiceType: 0x01, 95 | Reason: 0x02, 96 | }, 97 | } 98 | test := testCSBK(want, t) 99 | 100 | d, ok := test.Data.(*NegativeAcknowledgeResponse) 101 | switch { 102 | case !ok: 103 | t.Fatalf("decode failed: expected NegativeAcknowledgeResponse, got %T", test.Data) 104 | 105 | case d.ServiceType != 0x01: 106 | t.Fatalf("decode failed, service type wrong") 107 | 108 | case d.Reason != 0x02: 109 | t.Fatalf("decode failed, reason wrong") 110 | 111 | default: 112 | t.Logf("decode: %s", test.String()) 113 | } 114 | } 115 | 116 | func TestCSBKPreamble(t *testing.T) { 117 | want := &ControlBlock{ 118 | Opcode: PreambleOpcode, 119 | Data: &Preamble{ 120 | DataFollows: true, 121 | DstIsGroup: false, 122 | Blocks: 0x10, 123 | }, 124 | } 125 | test := testCSBK(want, t) 126 | 127 | d, ok := test.Data.(*Preamble) 128 | switch { 129 | case !ok: 130 | t.Fatalf("decode failed: expected Preamble, got %T", test.Data) 131 | 132 | case !d.DataFollows: 133 | t.Fatalf("decode failed, data follows wrong") 134 | 135 | case d.DstIsGroup: 136 | t.Fatalf("decode failed, dst is group wrong") 137 | 138 | case d.Blocks != 0x10: 139 | t.Fatalf("decode failed, blocks wrong") 140 | 141 | default: 142 | t.Logf("decode: %s", test.String()) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /crc.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | // G(x) = x^9+x^6+x^4+x^3+1 4 | func crc9(crc *uint16, b uint8, bits int) { 5 | var v uint8 = 0x80 6 | for i := 0; i < 8-bits; i++ { 7 | v >>= 1 8 | } 9 | for i := 0; i < 8; i++ { 10 | xor := (*crc)&0x0100 > 0 11 | (*crc) <<= 1 12 | // Limit the number of shift registers to 9. 13 | *crc &= 0x01ff 14 | if b&v > 0 { 15 | (*crc)++ 16 | } 17 | if xor { 18 | (*crc) ^= 0x0059 19 | } 20 | v >>= 1 21 | } 22 | } 23 | 24 | func crc9end(crc *uint16, bits int) { 25 | for i := 0; i < bits; i++ { 26 | xor := (*crc)&0x100 > 0 27 | (*crc) <<= 1 28 | // Limit the number of shift registers to 9. 29 | *crc &= 0x01ff 30 | if xor { 31 | (*crc) ^= 0x0059 32 | } 33 | } 34 | } 35 | 36 | // G(x) = x^16+x^12+x^5+1 37 | func crc16(crc *uint16, b byte) { 38 | var v uint8 = 0x80 39 | for i := 0; i < 8; i++ { 40 | xor := ((*crc) & 0x8000) != 0 41 | (*crc) <<= 1 42 | if b&v > 0 { 43 | (*crc)++ 44 | } 45 | if xor { 46 | (*crc) ^= 0x1021 47 | } 48 | v >>= 1 49 | } 50 | } 51 | 52 | func crc16end(crc *uint16) { 53 | for i := 0; i < 16; i++ { 54 | xor := ((*crc) & 0x8000) != 0 55 | (*crc) <<= 1 56 | if xor { 57 | (*crc) ^= 0x1021 58 | } 59 | } 60 | } 61 | 62 | func crc32(crc *uint32, b byte) { 63 | var v uint8 = 0x80 64 | for i := 0; i < 8; i++ { 65 | xor := ((*crc) & 0x80000000) > 0 66 | (*crc) <<= 1 67 | if b&v > 0 { 68 | (*crc)++ 69 | } 70 | if xor { 71 | (*crc) ^= 0x04c11db7 72 | } 73 | v >>= 1 74 | } 75 | } 76 | 77 | func crc32end(crc *uint32) { 78 | for i := 0; i < 32; i++ { 79 | xor := ((*crc) & 0x80000000) > 0 80 | (*crc) <<= 1 81 | if xor { 82 | (*crc) ^= 0x04c11db7 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crc/crc16/ctc16.go: -------------------------------------------------------------------------------- 1 | // Package crc16 implements the 16-bit cyclic redundancy check, or CRC-16, 2 | // checksum. See http://en.wikipedia.org/wiki/Cyclic_redundancy_check for 3 | // information. 4 | package crc16 5 | 6 | // Predefined polynomnials 7 | const ( 8 | // Used by X.25, V.41, HDLC FCS, XMODEM, Bluetooth, PACTOR, SD, ... 9 | CCITT = 0x8408 10 | ) 11 | 12 | // Table is a 256-word table representing the polynomial for efficient processing. 13 | type Table [256]uint16 14 | 15 | var ( 16 | CCITTTable = makeTable(CCITT) 17 | ) 18 | 19 | // MakeTable returns the Table constructed from the specified polynomial. 20 | func MakeTable(poly uint16) *Table { 21 | return makeTable(poly) 22 | } 23 | 24 | // makeTable returns the Table constructed from the specified polynomial. 25 | func makeTable(poly uint16) *Table { 26 | t := new(Table) 27 | for i := 0; i < 256; i++ { 28 | crc := uint16(i) 29 | for j := 0; j < 8; j++ { 30 | if crc&1 == 1 { 31 | crc = (crc >> 1) ^ poly 32 | } else { 33 | crc >>= 1 34 | } 35 | } 36 | t[i] = crc 37 | } 38 | return t 39 | } 40 | 41 | // Update returns the result of adding the bytes in p to the crc. 42 | func Update(crc uint16, tab *Table, p []byte) uint16 { 43 | return update(crc, tab, p) 44 | } 45 | 46 | func update(crc uint16, tab *Table, p []byte) uint16 { 47 | crc = ^crc 48 | for _, v := range p { 49 | crc = tab[byte(crc)^v] ^ (crc >> 8) 50 | } 51 | return ^crc 52 | } 53 | 54 | // Checksum returns the CRC-16 checksum of data using the polynomial represented by the Table. 55 | func Checksum(data []byte, tab *Table) uint16 { 56 | return Update(0, tab, data) 57 | } 58 | 59 | // ChecksumCCITT returns the CRC-16 checksum of data using the CCITT polynomial. 60 | func ChecksumCCITT(data []byte) uint16 { 61 | return update(0, CCITTTable, data) 62 | } 63 | -------------------------------------------------------------------------------- /crc/quadres_16_7/quadres_16_7.go: -------------------------------------------------------------------------------- 1 | // Package quadres_16_7 implements the quadratic residue (16, 7, 6) parity check. 2 | package quadres_16_7 3 | 4 | import "bytes" 5 | 6 | var ( 7 | validDataParities = [128][]byte{} 8 | ) 9 | 10 | type Codeword struct { 11 | Data []byte 12 | Parity []byte 13 | } 14 | 15 | func NewCodeword(bits []byte) *Codeword { 16 | if len(bits) < 16 { 17 | return nil 18 | } 19 | 20 | return &Codeword{ 21 | Data: bits[:7], 22 | Parity: bits[7:16], 23 | } 24 | } 25 | 26 | func ParityBits(bits []byte) []byte { 27 | parity := make([]byte, 9) 28 | // Multiplying the generator matrix with the given data bits. 29 | // See DMR AI spec. page 134. 30 | parity[0] = bits[1] ^ bits[2] ^ bits[3] ^ bits[4] 31 | parity[1] = bits[2] ^ bits[3] ^ bits[4] ^ bits[5] 32 | parity[2] = bits[0] ^ bits[3] ^ bits[4] ^ bits[5] ^ bits[6] 33 | parity[3] = bits[2] ^ bits[3] ^ bits[5] ^ bits[6] 34 | parity[4] = bits[1] ^ bits[2] ^ bits[6] 35 | parity[5] = bits[0] ^ bits[1] ^ bits[4] 36 | parity[6] = bits[0] ^ bits[1] ^ bits[2] ^ bits[5] 37 | parity[7] = bits[0] ^ bits[1] ^ bits[2] ^ bits[3] ^ bits[6] 38 | parity[8] = bits[0] ^ bits[2] ^ bits[4] ^ bits[5] ^ bits[6] 39 | return parity 40 | } 41 | 42 | func Check(bits []byte) bool { 43 | codeword := NewCodeword(bits) 44 | if codeword == nil { 45 | return false 46 | } 47 | 48 | var dataval uint8 49 | for col := uint8(0); col < 7; col++ { 50 | if codeword.Data[col] == 1 { 51 | dataval |= (1 << (7 - col)) 52 | } 53 | } 54 | 55 | return bytes.Equal(codeword.Parity, validDataParities[dataval]) 56 | } 57 | 58 | func toBits(b byte) []byte { 59 | var o = make([]byte, 8) 60 | for bit, mask := 0, byte(128); bit < 8; bit, mask = bit+1, mask>>1 { 61 | if b&mask != 0 { 62 | o[bit] = 1 63 | } 64 | } 65 | return o 66 | } 67 | 68 | func init() { 69 | for i := byte(0); i < 128; i++ { 70 | bits := toBits(i) 71 | validDataParities[i] = ParityBits(bits) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /crc_test.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import "testing" 4 | 5 | func TestCRC9(t *testing.T) { 6 | tests := map[uint16][]byte{ 7 | 0x0000: []byte{}, 8 | 0x0100: []byte{0x00, 0x01}, 9 | 0x0179: []byte("hello world"), 10 | } 11 | 12 | for want, test := range tests { 13 | var crc uint16 14 | for _, b := range test { 15 | crc9(&crc, b, 8) 16 | } 17 | crc9end(&crc, 8) 18 | if crc != want { 19 | t.Fatalf("crc9 %v failed: %#04x != %#04x", test, crc, want) 20 | } 21 | } 22 | } 23 | 24 | func TestCRC16(t *testing.T) { 25 | tests := map[uint16][]byte{ 26 | 0x0000: []byte{}, 27 | 0x1021: []byte{0x00, 0x01}, 28 | 0x3be4: []byte("hello world"), 29 | } 30 | 31 | for want, test := range tests { 32 | var crc uint16 33 | for _, b := range test { 34 | crc16(&crc, b) 35 | } 36 | crc16end(&crc) 37 | if crc != want { 38 | t.Fatalf("crc16 %v failed: %#04x != %#04x", test, crc, want) 39 | } 40 | } 41 | } 42 | 43 | func TestCRC32(t *testing.T) { 44 | tests := map[uint32][]byte{ 45 | 0x00000000: []byte{}, 46 | 0x04c11db7: []byte{0x00, 0x01}, 47 | 0x737af2ae: []byte("hello world"), 48 | } 49 | 50 | for want, test := range tests { 51 | var crc uint32 52 | for _, b := range test { 53 | crc32(&crc, b) 54 | } 55 | crc32end(&crc) 56 | if crc != want { 57 | t.Fatalf("crc32 %v failed: %#08x != %#08x", test, crc, want) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /data.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | 8 | "golang.org/x/text/encoding" 9 | "golang.org/x/text/encoding/charmap" 10 | "golang.org/x/text/encoding/unicode" 11 | ) 12 | 13 | const ( 14 | // n_DFragMax, see DMR AI spec. page 163. 15 | MaxPacketFragmentSize = 1500 16 | ) 17 | 18 | // CRC Masks for data block's CRC-9 calculation, see DMR AI spec. page 148 (Table B.21). 19 | var crc9Masks = map[uint8]uint16{ 20 | Rate12Data: 0x00f0, 21 | Rate34Data: 0x01ff, 22 | //Rate1Data: 0x010f, 23 | } 24 | 25 | func calculateCRC9(serial uint8, data []byte, dataType uint8) (crc uint16) { 26 | for _, block := range data { 27 | crc9(&crc, block, 8) 28 | } 29 | crc9(&crc, serial, 7) 30 | crc9end(&crc, 8) 31 | 32 | // Inverting according to the inversion polynomial. 33 | crc = ^crc 34 | crc &= 0x01ff 35 | 36 | // Applying Data Type CRC Mask 37 | crc ^= crc9Masks[dataType] 38 | 39 | return crc 40 | } 41 | 42 | type DataBlock struct { 43 | Serial uint8 44 | CRC uint16 45 | OK bool 46 | Data []byte 47 | Length uint8 48 | } 49 | 50 | func ParseDataBlock(data []byte, dataType uint8, confirmed bool) (*DataBlock, error) { 51 | var ( 52 | crc uint16 53 | db = &DataBlock{ 54 | Length: dataBlockLength(dataType, confirmed), 55 | } 56 | ) 57 | 58 | if confirmed { 59 | db.Serial = data[0] >> 1 60 | db.CRC = uint16(data[0]&B00000001)<<8 | uint16(data[1]) 61 | db.Data = make([]byte, db.Length) 62 | copy(db.Data, data[2:2+db.Length]) 63 | 64 | crc = calculateCRC9(db.Serial, db.Data, dataType) 65 | 66 | // FIXME(pd0mz): this is not working 67 | if crc != db.CRC { 68 | return nil, fmt.Errorf("dmr: block CRC error (%#04x != %#04x)", crc, db.CRC) 69 | } 70 | } else { 71 | db.Data = make([]byte, db.Length) 72 | copy(db.Data, data[:db.Length]) 73 | } 74 | 75 | db.OK = true 76 | return db, nil 77 | } 78 | 79 | func (db *DataBlock) Bytes(dataType uint8, confirmed bool) []byte { 80 | var ( 81 | size = dataBlockLength(dataType, confirmed) 82 | data = make([]byte, size) 83 | ) 84 | 85 | if confirmed { 86 | db.CRC = calculateCRC9(db.Serial, db.Data, dataType) 87 | 88 | // Grow data slice to support the two byte prefix 89 | data = append(data, make([]byte, 2)...) 90 | 91 | data[0] = (db.Serial << 1) | (uint8(db.CRC>>8) & 0x01) 92 | data[1] = uint8(db.CRC) 93 | copy(data[2:], db.Data) 94 | } else { 95 | copy(data, db.Data) 96 | } 97 | 98 | return data 99 | } 100 | 101 | func dataBlockLength(dataType uint8, confirmed bool) uint8 { 102 | var size uint8 103 | 104 | switch dataType { 105 | case Data: 106 | size = 22 107 | break 108 | case Rate12Data: 109 | size = 10 110 | break 111 | case Rate34Data: 112 | size = 16 113 | break 114 | default: 115 | return 0 116 | } 117 | 118 | if !confirmed { 119 | return size + 2 120 | } 121 | return size 122 | } 123 | 124 | type DataFragment struct { 125 | Data []byte 126 | Stored int 127 | Needed int 128 | CRC uint32 129 | } 130 | 131 | func (df *DataFragment) DataBlocks(dataType uint8, confirm bool) ([]*DataBlock, error) { 132 | df.Stored = len(df.Data) 133 | if df.Stored > MaxPacketFragmentSize { 134 | df.Stored = MaxPacketFragmentSize 135 | } 136 | 137 | // See DMR AI spec. page. 73. for block sizes. 138 | var size = int(dataBlockLength(dataType, confirm)) 139 | df.Needed = (df.Stored + size - 1) / size 140 | 141 | // Leave enough room for the 4 bytes CRC32 142 | if (df.Needed*size)-df.Stored < 4 { 143 | df.Needed++ 144 | } 145 | 146 | // Calculate fragment CRC32 147 | for i := 0; i < (df.Needed*size)-4; i += 2 { 148 | if i+1 < df.Stored { 149 | crc32(&df.CRC, df.Data[i+1]) 150 | } else { 151 | crc32(&df.CRC, 0) 152 | } 153 | if i < df.Stored { 154 | crc32(&df.CRC, df.Data[i]) 155 | } else { 156 | crc32(&df.CRC, 0) 157 | } 158 | } 159 | crc32end(&df.CRC) 160 | 161 | var ( 162 | blocks = make([]*DataBlock, df.Needed) 163 | stored int 164 | ) 165 | for i := range blocks { 166 | block := &DataBlock{ 167 | Serial: uint8(i % 128), 168 | Length: dataBlockLength(dataType, confirm), 169 | } 170 | block.Data = make([]byte, block.Length) 171 | 172 | store := int(block.Length) 173 | if df.Stored-stored < store { 174 | store = df.Stored - stored 175 | } 176 | copy(block.Data, df.Data[stored:stored+store]) 177 | stored += store 178 | 179 | if i == (df.Needed - 1) { 180 | block.Data[block.Length-1] = uint8(df.CRC >> 24) 181 | block.Data[block.Length-2] = uint8(df.CRC >> 16) 182 | block.Data[block.Length-3] = uint8(df.CRC >> 8) 183 | block.Data[block.Length-4] = uint8(df.CRC) 184 | } 185 | 186 | // Calculate block CRC9 187 | block.CRC = calculateCRC9(block.Serial, block.Data, dataType) 188 | 189 | blocks[i] = block 190 | } 191 | 192 | return blocks, nil 193 | } 194 | 195 | func CombineDataBlocks(blocks []*DataBlock) (*DataFragment, error) { 196 | if blocks == nil || len(blocks) == 0 { 197 | return nil, errors.New("dmr: no data blocks to combine") 198 | } 199 | 200 | f := &DataFragment{ 201 | Data: make([]byte, MaxPacketFragmentSize), 202 | } 203 | for i, block := range blocks { 204 | if block.Length == 0 { 205 | continue 206 | } 207 | if i < (len(blocks) - 1) { 208 | if f.Stored+int(block.Length) < len(f.Data) { 209 | copy(f.Data[f.Stored:], block.Data[:block.Length]) 210 | f.Stored += int(block.Length) 211 | } 212 | } else { 213 | if f.Stored+int(block.Length)-4 < len(f.Data) { 214 | copy(f.Data[f.Stored:], block.Data[:block.Length]) 215 | f.Stored += int(block.Length) 216 | } 217 | f.CRC = 0 218 | f.CRC |= uint32(block.Data[block.Length-4]) 219 | f.CRC |= uint32(block.Data[block.Length-3]) << 8 220 | f.CRC |= uint32(block.Data[block.Length-2]) << 16 221 | f.CRC |= uint32(block.Data[block.Length-1]) << 24 222 | } 223 | } 224 | 225 | var crc uint32 226 | for i := 0; i < f.Stored-4; i += 2 { 227 | crc32(&crc, f.Data[i+1]) 228 | crc32(&crc, f.Data[i]) 229 | } 230 | crc32end(&crc) 231 | 232 | if crc != f.CRC { 233 | return nil, fmt.Errorf("dmr: fragment CRC error (%#08x != %#08x)", crc, f.CRC) 234 | } 235 | return f, nil 236 | } 237 | 238 | var encodingMap map[uint8]encoding.Encoding 239 | 240 | func BuildMessageData(msg string, ddFormat uint8, nullTerminated bool) ([]byte, error) { 241 | if e, ok := encodingMap[ddFormat]; ok { 242 | enc := e.NewEncoder() 243 | data, err := enc.Bytes([]byte(msg)) 244 | if err != nil { 245 | return nil, err 246 | } 247 | if nullTerminated { 248 | data = append(data, []byte{0x00, 0x00}...) 249 | } 250 | return data, nil 251 | } 252 | return nil, fmt.Errorf("dmr: encoding %s text is not supported", DDFormatName[ddFormat]) 253 | } 254 | 255 | func ParseMessageData(data []byte, ddFormat uint8, nullTerminated bool) (string, error) { 256 | if e, ok := encodingMap[ddFormat]; ok { 257 | dec := e.NewDecoder() 258 | str, err := dec.Bytes(data) 259 | if err != nil { 260 | return "", err 261 | } 262 | if nullTerminated { 263 | if idx := bytes.IndexByte(str, 0x00); idx >= 0 { 264 | str = str[:idx] 265 | } 266 | } 267 | return string(str), nil 268 | } 269 | return "", fmt.Errorf("dmr: decoding %s text is not supported", DDFormatName[ddFormat]) 270 | } 271 | 272 | func init() { 273 | encodingMap = map[uint8]encoding.Encoding{ 274 | DDFormatBinary: binaryEncoding{}, 275 | DDFormat8BitISO8859_2: charmap.ISO8859_2, 276 | DDFormat8BitISO8859_3: charmap.ISO8859_3, 277 | DDFormat8BitISO8859_4: charmap.ISO8859_4, 278 | DDFormat8BitISO8859_5: charmap.ISO8859_5, 279 | DDFormat8BitISO8859_6: charmap.ISO8859_6, 280 | DDFormat8BitISO8859_7: charmap.ISO8859_7, 281 | DDFormat8BitISO8859_8: charmap.ISO8859_8, 282 | DDFormat8BitISO8859_10: charmap.ISO8859_10, 283 | DDFormat8BitISO8859_13: charmap.ISO8859_13, 284 | DDFormat8BitISO8859_14: charmap.ISO8859_14, 285 | DDFormat8BitISO8859_15: charmap.ISO8859_15, 286 | DDFormat8BitISO8859_16: charmap.ISO8859_16, 287 | DDFormatUTF8: unicode.UTF8, 288 | DDFormatUTF16: unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM), 289 | DDFormatUTF16BE: unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM), 290 | DDFormatUTF16LE: unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM), 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /data_encoding.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "golang.org/x/text/encoding" 5 | "golang.org/x/text/transform" 6 | ) 7 | 8 | type binaryCoder struct{ transform.NopResetter } 9 | 10 | func (e binaryCoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { 11 | // The decoder can only make the input larger, not smaller. 12 | n := len(src) 13 | if len(dst) < n { 14 | err = transform.ErrShortDst 15 | n = len(dst) 16 | atEOF = false 17 | } else { 18 | copy(dst[:n], src) 19 | nDst = n 20 | nSrc = n 21 | } 22 | return 23 | } 24 | 25 | type binaryEncoding struct{} 26 | 27 | func (e binaryEncoding) NewDecoder() *encoding.Decoder { 28 | return &encoding.Decoder{Transformer: binaryCoder{}} 29 | } 30 | 31 | func (e binaryEncoding) NewEncoder() *encoding.Encoder { 32 | return &encoding.Encoder{Transformer: binaryCoder{}} 33 | } 34 | -------------------------------------------------------------------------------- /data_test.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "sort" 8 | "testing" 9 | ) 10 | 11 | func TestDataBlock(t *testing.T) { 12 | want := &DataBlock{ 13 | Serial: 123, 14 | Data: []byte{0x17, 0x2a}, 15 | Length: 2, 16 | } 17 | 18 | data := want.Bytes(Rate34Data, true) 19 | if data == nil { 20 | t.Fatal("encode failed") 21 | } 22 | // Size is the user-data + two octets of serial/crc 23 | size := int(dataBlockLength(Rate34Data, true)) + 2 24 | if len(data) != size { 25 | t.Fatalf("encode failed: expected %d bytes, got %d", size, len(data)) 26 | } 27 | 28 | // Decoding is tested in the DataFragment test 29 | } 30 | 31 | func TestDataFragment(t *testing.T) { 32 | msg, err := BuildMessageData("CQCQCQ PD0MZ", DDFormatUTF16, true) 33 | if err != nil { 34 | t.Fatalf("build message failed: %v", err) 35 | } 36 | 37 | want := &DataFragment{Data: msg} 38 | blocks, err := want.DataBlocks(Rate34Data, true) 39 | if err != nil { 40 | t.Fatalf("encode failed: %v", err) 41 | } 42 | if blocks == nil { 43 | t.Fatal("encode failed: blocks is nil") 44 | } 45 | if len(blocks) != 2 { 46 | t.Fatalf("encode failed: expected 2 blocks, got %d", len(blocks)) 47 | } 48 | 49 | for i, block := range blocks { 50 | t.Log(fmt.Sprintf("block %02d:\n%s", i, hex.Dump(block.Data))) 51 | } 52 | 53 | test, err := CombineDataBlocks(blocks) 54 | if err != nil { 55 | t.Fatalf("decode failed: %v", err) 56 | } 57 | 58 | if !bytes.Equal(test.Data[:len(want.Data)], want.Data) { 59 | t.Log(fmt.Sprintf("want:\n%s", hex.Dump(want.Data))) 60 | t.Log(fmt.Sprintf("got:\n%s", hex.Dump(test.Data))) 61 | t.Fatal("decode failed: data is wrong") 62 | } 63 | } 64 | 65 | func TestMessage(t *testing.T) { 66 | msg := "CQCQCQ PD0MZ" 67 | 68 | var encodings = []int{} 69 | for i := range encodingMap { 70 | encodings = append(encodings, int(i)) 71 | } 72 | sort.Sort(sort.IntSlice(encodings)) 73 | 74 | for _, i := range encodings { 75 | e := encodingMap[uint8(i)] 76 | n := DDFormatName[uint8(i)] 77 | t.Logf("testing %s encoding", n) 78 | 79 | enc := e.NewDecoder() 80 | str, err := enc.String(msg) 81 | if err != nil { 82 | t.Fatalf("error encoding to %s: %v", n, err) 83 | } 84 | 85 | dec := e.NewDecoder() 86 | out, err := dec.String(str) 87 | if err != nil { 88 | t.Fatalf("error decoding from %s: %v", n, err) 89 | } 90 | 91 | t.Log(fmt.Sprintf("encoder:\n%s", hex.Dump([]byte(str)))) 92 | t.Log(fmt.Sprintf("decoder:\n%s", hex.Dump([]byte(out)))) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /dataheader.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Data Header Packet Format 9 | const ( 10 | PacketFormatUDT uint8 = iota // 0b0000 11 | PacketFormatResponse // 0b0001 12 | PacketFormatUnconfirmedData // 0b0010 13 | PacketFormatConfirmedData // 0b0011 14 | _ // 0b0100 15 | _ // 0b0101 16 | _ // 0b0110 17 | _ // 0b0111 18 | _ // 0b1000 19 | _ // 0b1001 20 | _ // 0b1010 21 | _ // 0b1011 22 | _ // 0b1100 23 | PacketFormatShortDataDefined // 0b1101 24 | PacketFormatShortDataRaw // 0b1110 25 | PacketFormatProprietaryData // 0b1111 26 | ) 27 | 28 | // Service Access Point 29 | const ( 30 | ServiceAccessPointUDT uint8 = iota // 0b0000 31 | _ // 0b0001 32 | ServiceAccessPointTCPIPHeaderCompression // 0b0010 33 | ServiceAccessPointUDPIPHeaderCompression // 0b0011 34 | ServiceAccessPointIPBasedPacketData // 0b0100 35 | ServiceAccessPointARP // 0b0101 36 | _ // 0b0110 37 | _ // 0b0111 38 | _ // 0b1000 39 | ServiceAccessPointProprietaryData // 0b1001 40 | ServiceAccessPointShortData // 0b1010 41 | ) 42 | 43 | var ServiceAccessPointName = map[uint8]string{ 44 | ServiceAccessPointUDT: "UDT", 45 | ServiceAccessPointTCPIPHeaderCompression: "TCP/IP header compression", 46 | ServiceAccessPointUDPIPHeaderCompression: "UDP/IP header compression", 47 | ServiceAccessPointIPBasedPacketData: "IP based packet data", 48 | ServiceAccessPointARP: "ARP", 49 | ServiceAccessPointProprietaryData: "proprietary data", 50 | ServiceAccessPointShortData: "short data", 51 | } 52 | 53 | // Response Data Header Response Type, encodes class and type 54 | const ( 55 | _ uint8 = iota // Class 0b00, Type 0b000 56 | ResponseTypeACK // Class 0b00, Type 0b001 57 | _ // Class 0b00, Type 0b010 58 | _ // Class 0b00, Type 0b011 59 | _ // Class 0b00, Type 0b100 60 | _ // Class 0b00, Type 0b101 61 | _ // Class 0b00, Type 0b110 62 | _ // Class 0b00, Type 0b111 63 | ResponseTypeIllegalFormat // Class 0b01, Type 0b000 64 | ResponseTypePacketCRCFailed // Class 0b01, Type 0b001 65 | ResponseTypeMemoryFull // Class 0b01, Type 0b010 66 | ResponseTypeRecvFSVNOutOfSeq // Class 0b01, Type 0b011 67 | ResponseTypeUndeliverable // Class 0b01, Type 0b100 68 | ResponseTypeRecvPktOutOfSeq // Class 0b01, Type 0b101 69 | ResponseTypeDisallowed // Class 0b01, Type 0b110 70 | _ // Class 0b01, Type 0b111 71 | ResponseTypeSelectiveACK // Class 0b10, Type 0b000 72 | ) 73 | 74 | var ResponseTypeName = map[uint8]string{ 75 | ResponseTypeACK: "ACK", 76 | ResponseTypeIllegalFormat: "illegal format", 77 | ResponseTypePacketCRCFailed: "packet CRC failed", 78 | ResponseTypeMemoryFull: "memory full", 79 | ResponseTypeRecvFSVNOutOfSeq: "recv FSN out of sequence", 80 | ResponseTypeUndeliverable: "undeliverable", 81 | ResponseTypeRecvPktOutOfSeq: "recv PKT our of sequence", 82 | ResponseTypeDisallowed: "disallowed", 83 | ResponseTypeSelectiveACK: "selective ACK", 84 | } 85 | 86 | // UDP Response Header UDT Format 87 | const ( 88 | UDTFormatBinary uint8 = iota 89 | UDTFormatMSAddress 90 | UDTFormat4BitBCD 91 | UDTFormatISO_7BitChars 92 | UDTFormatISO_8BitChars 93 | UDTFormatNMEALocation 94 | UDTFormatIPAddress 95 | UDTFormat16BitUnicodeChars 96 | UDTFormatCustomCodeD1 97 | UDTFormatCustomCodeD2 98 | ) 99 | 100 | var UDTFormatName = map[uint8]string{ 101 | UDTFormatBinary: "binary", 102 | UDTFormatMSAddress: "MS address", 103 | UDTFormat4BitBCD: "4-bit BCD", 104 | UDTFormatISO_7BitChars: "ISO 7-bit characters", 105 | UDTFormatISO_8BitChars: "ISO 8-bit characters", 106 | UDTFormatNMEALocation: "NMEA location", 107 | UDTFormatIPAddress: "IP address", 108 | UDTFormat16BitUnicodeChars: "16-bit Unicode characters", 109 | UDTFormatCustomCodeD1: "custom code D1", 110 | UDTFormatCustomCodeD2: "custom code D2", 111 | } 112 | 113 | // UDT Response Header DD Format 114 | const ( 115 | DDFormatBinary uint8 = iota 116 | DDFormatBCD 117 | DDFormat7BitChar 118 | DDFormat8BitISO8859_1 119 | DDFormat8BitISO8859_2 120 | DDFormat8BitISO8859_3 121 | DDFormat8BitISO8859_4 122 | DDFormat8BitISO8859_5 123 | DDFormat8BitISO8859_6 124 | DDFormat8BitISO8859_7 125 | DDFormat8BitISO8859_8 126 | DDFormat8BitISO8859_9 127 | DDFormat8BitISO8859_10 128 | DDFormat8BitISO8859_11 129 | DDFormat8BitISO8859_13 130 | DDFormat8BitISO8859_14 131 | DDFormat8BitISO8859_15 132 | DDFormat8BitISO8859_16 133 | DDFormatUTF8 134 | DDFormatUTF16 135 | DDFormatUTF16BE 136 | DDFormatUTF16LE 137 | DDFormatUTF32 138 | DDFormatUTF32BE 139 | DDFormatUTF32LE 140 | ) 141 | 142 | var DDFormatName = map[uint8]string{ 143 | DDFormatBinary: "binary", 144 | DDFormatBCD: "BCD", 145 | DDFormat7BitChar: "7-bit characters", 146 | DDFormat8BitISO8859_1: "8-bit ISO 8859-1", 147 | DDFormat8BitISO8859_2: "8-bit ISO 8859-2", 148 | DDFormat8BitISO8859_3: "8-bit ISO 8859-3", 149 | DDFormat8BitISO8859_4: "8-bit ISO 8859-4", 150 | DDFormat8BitISO8859_5: "8-bit ISO 8859-5", 151 | DDFormat8BitISO8859_6: "8-bit ISO 8859-6", 152 | DDFormat8BitISO8859_7: "8-bit ISO 8859-7", 153 | DDFormat8BitISO8859_8: "8-bit ISO 8859-8", 154 | DDFormat8BitISO8859_9: "8-bit ISO 8859-9", 155 | DDFormat8BitISO8859_10: "8-bit ISO 8859-10", 156 | DDFormat8BitISO8859_11: "8-bit ISO 8859-11", 157 | DDFormat8BitISO8859_13: "8-bit ISO 8859-13", 158 | DDFormat8BitISO8859_14: "8-bit ISO 8859-14", 159 | DDFormat8BitISO8859_15: "8-bit ISO 8859-15", 160 | DDFormat8BitISO8859_16: "8-bit ISO 8859-16", 161 | DDFormatUTF8: "UTF-8", 162 | DDFormatUTF16: "UTF-16", 163 | DDFormatUTF16BE: "UTF-16 big endian", 164 | DDFormatUTF16LE: "UTF-16 little endian", 165 | DDFormatUTF32: "UTF-32", 166 | DDFormatUTF32BE: "UTF-32 big endian", 167 | DDFormatUTF32LE: "UTF-32 little endian", 168 | } 169 | 170 | // http://www.etsi.org/images/files/DMRcodes/dmrs-mfid.xls 171 | var ManufacturerName = map[uint8]string{ 172 | 0x00: "Reserved", 173 | 0x01: "Reserved", 174 | 0x02: "Reserved", 175 | 0x03: "Reserved", 176 | 0x04: "Flyde Micro Ltd.", 177 | 0x05: "PROD-EL SPA", 178 | 0x06: "Trident Datacom DBA Trident Micro Systems", 179 | 0x07: "RADIODATA GmbH", 180 | 0x08: "HYT science tech", 181 | 0x09: "ASELSAN Elektronik Sanayi ve Ticaret A.S.", 182 | 0x0a: "Kirisun Communications Co. Ltd", 183 | 0x0b: "DMR Association Ltd.", 184 | 0x10: "Motorola Ltd.", 185 | 0x13: "EMC S.p.A. (Electronic Marketing Company)", 186 | 0x1c: "EMC S.p.A. (Electronic Marketing Company)", 187 | 0x20: "JVCKENWOOD Corporation", 188 | 0x33: "Radio Activity Srl", 189 | 0x3c: "Radio Activity Srl", 190 | 0x58: "Tait Electronics Ltd", 191 | 0x68: "HYT science tech", 192 | 0x77: "Vertex Standard", 193 | } 194 | 195 | type DataHeaderData interface { 196 | String() string 197 | Write([]byte) error 198 | } 199 | 200 | type DataHeader struct { 201 | PacketFormat uint8 202 | DstIsGroup bool 203 | ResponseRequested bool 204 | HeaderCompression bool 205 | ServiceAccessPoint uint8 206 | DstID uint32 207 | SrcID uint32 208 | CRC uint16 209 | Data DataHeaderData 210 | } 211 | 212 | func (h *DataHeader) Bytes() ([]byte, error) { 213 | var data = make([]byte, 12) 214 | 215 | data[0] = (h.PacketFormat & B00001111) 216 | if h.DstIsGroup { 217 | data[0] |= B10000000 218 | } 219 | if h.ResponseRequested { 220 | data[0] |= B01000000 221 | } 222 | if h.HeaderCompression { 223 | data[0] |= B00100000 224 | } 225 | data[1] = (h.ServiceAccessPoint << 4) & B11110000 226 | data[2] = uint8(h.DstID >> 16) 227 | data[3] = uint8(h.DstID >> 8) 228 | data[4] = uint8(h.DstID) 229 | data[5] = uint8(h.SrcID >> 16) 230 | data[6] = uint8(h.SrcID >> 8) 231 | data[7] = uint8(h.SrcID) 232 | 233 | if h.Data != nil { 234 | if err := h.Data.Write(data); err != nil { 235 | return nil, err 236 | } 237 | } 238 | 239 | h.CRC = 0 240 | for i := 0; i < 10; i++ { 241 | crc16(&h.CRC, data[i]) 242 | } 243 | crc16end(&h.CRC) 244 | 245 | // Inverting according to the inversion polynomial. 246 | h.CRC = ^h.CRC 247 | // Applying CRC mask, see DMR AI spec. page 143. 248 | h.CRC ^= 0xcccc 249 | 250 | data[10] = uint8(h.CRC >> 8) 251 | data[11] = uint8(h.CRC) 252 | 253 | return data, nil 254 | } 255 | 256 | func (h DataHeader) String() string { 257 | var part = []string{"data header"} 258 | if h.DstIsGroup { 259 | part = append(part, "group") 260 | } else { 261 | part = append(part, "unit") 262 | } 263 | part = append(part, fmt.Sprintf("response %t, sap %s (%d), %d->%d", 264 | h.ResponseRequested, ServiceAccessPointName[h.ServiceAccessPoint], h.ServiceAccessPoint, 265 | h.SrcID, h.DstID)) 266 | if h.Data != nil { 267 | part = append(part, h.Data.String()) 268 | } 269 | return strings.Join(part, ", ") 270 | } 271 | 272 | type UDTData struct { 273 | Format uint8 274 | PadNibble uint8 275 | AppendedBlocks uint8 276 | SupplementaryFlag bool 277 | Opcode uint8 278 | } 279 | 280 | func (d UDTData) String() string { 281 | return fmt.Sprintf("UDT, format %s (%d), pad nibble %d, appended blocks %d, supplementary %t, opcode %d", 282 | UDTFormatName[d.Format], d.Format, d.PadNibble, d.AppendedBlocks, d.SupplementaryFlag, d.Opcode) 283 | } 284 | 285 | func (d UDTData) Write(data []byte) error { 286 | data[1] |= (d.Format & B00001111) 287 | data[8] = (d.AppendedBlocks & B00000011) | (d.PadNibble << 3) 288 | data[9] = (d.Opcode & B00111111) 289 | if d.SupplementaryFlag { 290 | data[9] |= B10000000 291 | } 292 | return nil 293 | } 294 | 295 | type UnconfirmedData struct { 296 | PadOctetCount uint8 297 | FullMessage bool 298 | BlocksToFollow uint8 299 | FragmentSequenceNumber uint8 300 | } 301 | 302 | func (d UnconfirmedData) String() string { 303 | return fmt.Sprintf("unconfirmed, pad octet %d, full %t, blocks %d, sequence %d", 304 | d.PadOctetCount, d.FullMessage, d.BlocksToFollow, d.FragmentSequenceNumber) 305 | } 306 | 307 | func (d UnconfirmedData) Write(data []byte) error { 308 | data[0] |= d.PadOctetCount & B00010000 309 | data[1] |= d.PadOctetCount & B00001111 310 | data[8] = d.BlocksToFollow & B01111111 311 | if d.FullMessage { 312 | data[8] |= B10000000 313 | } 314 | data[9] = d.FragmentSequenceNumber & B00001111 315 | return nil 316 | } 317 | 318 | type ConfirmedData struct { 319 | PadOctetCount uint8 320 | FullMessage bool 321 | BlocksToFollow uint8 322 | Resync bool 323 | SendSequenceNumber uint8 324 | FragmentSequenceNumber uint8 325 | } 326 | 327 | func (d ConfirmedData) String() string { 328 | return fmt.Sprintf("confirmed, pad octet %d, full %t, blocks %d, resync %t, send sequence %d, sequence %d", 329 | d.PadOctetCount, d.FullMessage, d.BlocksToFollow, d.Resync, d.SendSequenceNumber, d.FragmentSequenceNumber) 330 | } 331 | 332 | func (d ConfirmedData) Write(data []byte) error { 333 | data[0] |= d.PadOctetCount & B00010000 334 | data[1] |= d.PadOctetCount & B00001111 335 | data[8] = d.BlocksToFollow & B01111111 336 | if d.FullMessage { 337 | data[8] |= B10000000 338 | } 339 | data[9] = (d.FragmentSequenceNumber&B00000111)<<0 | (d.SendSequenceNumber&B00000111)<<4 340 | if d.Resync { 341 | data[9] |= B10000000 342 | } 343 | return nil 344 | } 345 | 346 | type ResponseData struct { 347 | BlocksToFollow uint8 348 | ClassType uint8 // See ResponseType map above 349 | Status uint8 350 | } 351 | 352 | func (d ResponseData) String() string { 353 | return fmt.Sprintf("response, blocks %d, type %s (%02b %03b), status %d", 354 | d.BlocksToFollow, ResponseTypeName[d.ClassType], (d.ClassType >> 3), (d.ClassType & 0x07), d.Status) 355 | } 356 | 357 | func (d ResponseData) Write(data []byte) error { 358 | data[8] = d.BlocksToFollow & B01111111 359 | data[9] = d.Status | d.ClassType<<3 360 | return nil 361 | } 362 | 363 | type ProprietaryData struct { 364 | ManufacturerID uint8 365 | } 366 | 367 | func (d ProprietaryData) String() string { 368 | return fmt.Sprintf("proprietary, manufacturer %s (%d)", 369 | ManufacturerName[d.ManufacturerID], d.ManufacturerID) 370 | } 371 | 372 | func (d ProprietaryData) Write(data []byte) error { 373 | data[1] = (d.ManufacturerID & B01111111) 374 | return nil 375 | } 376 | 377 | type ShortDataRawData struct { 378 | AppendedBlocks uint8 379 | SrcPort uint8 380 | DstPort uint8 381 | Resync bool 382 | FullMessage bool 383 | BitPadding uint8 384 | } 385 | 386 | func (d ShortDataRawData) String() string { 387 | return fmt.Sprintf("short data raw, blocks %d, src port %d, dst port %d, rsync %t, full %t, padding %d", 388 | d.AppendedBlocks, d.SrcPort, d.DstPort, d.Resync, d.FullMessage, d.BitPadding) 389 | } 390 | 391 | func (d ShortDataRawData) Write(data []byte) error { 392 | data[0] |= d.AppendedBlocks & B00110000 393 | data[1] |= d.AppendedBlocks & B00001111 394 | data[8] = (d.SrcPort&B00000111)<<5 | (d.DstPort&B00000111)<<2 395 | if d.Resync { 396 | data[8] |= B00000010 397 | } 398 | if d.FullMessage { 399 | data[8] |= B00000001 400 | } 401 | data[9] = d.BitPadding 402 | return nil 403 | } 404 | 405 | type ShortDataDefinedData struct { 406 | AppendedBlocks uint8 407 | DDFormat uint8 408 | Resync bool 409 | FullMessage bool 410 | BitPadding uint8 411 | } 412 | 413 | func (d ShortDataDefinedData) String() string { 414 | return fmt.Sprintf("short data defined, blocks %d, dd format %s (%d), resync %t, full %t, padding %d", 415 | d.AppendedBlocks, DDFormatName[d.DDFormat], d.DDFormat, d.Resync, d.FullMessage, d.BitPadding) 416 | } 417 | 418 | func (d ShortDataDefinedData) Write(data []byte) error { 419 | data[0] |= d.AppendedBlocks & B00110000 420 | data[1] |= d.AppendedBlocks & B00001111 421 | data[8] = (d.DDFormat & B00111111) << 2 422 | if d.Resync { 423 | data[8] |= B00000010 424 | } 425 | if d.FullMessage { 426 | data[8] |= B00000001 427 | } 428 | data[9] = d.BitPadding 429 | return nil 430 | } 431 | 432 | var _ (DataHeaderData) = (*ShortDataDefinedData)(nil) 433 | 434 | func ParseDataHeader(data []byte, proprietary bool) (*DataHeader, error) { 435 | if len(data) != 12 { 436 | return nil, fmt.Errorf("data must be 12 bytes, got %d", len(data)) 437 | } 438 | var ( 439 | ccrc = (uint16(data[10]) << 8) | uint16(data[11]) 440 | hcrc = dataHeaderCRC(data) 441 | ) 442 | if ccrc != hcrc { 443 | return nil, fmt.Errorf("data CRC mismatch, %#04x != %#04x", ccrc, hcrc) 444 | } 445 | 446 | h := &DataHeader{ 447 | DstIsGroup: (data[0] & B10000000) > 0, 448 | ResponseRequested: (data[0] & B01000000) > 0, 449 | HeaderCompression: (data[0] & B00100000) > 0, 450 | PacketFormat: (data[0] & B00001111), 451 | ServiceAccessPoint: (data[1] & B11110000) >> 4, 452 | DstID: uint32(data[2])<<16 | uint32(data[3])<<8 | uint32(data[4]), 453 | SrcID: uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]), 454 | CRC: ccrc, 455 | } 456 | 457 | if proprietary { 458 | h.Data = ProprietaryData{ 459 | ManufacturerID: data[1] & B01111111, 460 | } 461 | 462 | } else { 463 | switch h.PacketFormat { 464 | case PacketFormatUDT: 465 | h.Data = &UDTData{ 466 | Format: (data[1] & B00001111), 467 | PadNibble: (data[8] & B11111000) >> 3, 468 | AppendedBlocks: (data[8] & B00000011), 469 | SupplementaryFlag: (data[9] & B10000000) > 0, 470 | Opcode: (data[9] & B00111111), 471 | } 472 | break 473 | 474 | case PacketFormatResponse: 475 | h.Data = &ResponseData{ 476 | BlocksToFollow: (data[8] & B01111111), 477 | ClassType: (data[9] & B11111000) >> 3, 478 | Status: (data[9] & B00000111), 479 | } 480 | break 481 | 482 | case PacketFormatUnconfirmedData: 483 | h.Data = &UnconfirmedData{ 484 | PadOctetCount: (data[0] & B00010000) | (data[1] & B00001111), 485 | FullMessage: (data[8] & B10000000) > 0, 486 | BlocksToFollow: (data[8] & B01111111), 487 | FragmentSequenceNumber: (data[9] & B00001111), 488 | } 489 | break 490 | 491 | case PacketFormatConfirmedData: 492 | h.Data = &ConfirmedData{ 493 | PadOctetCount: (data[0] & B00010000) | (data[1] & B00001111), 494 | FullMessage: (data[8] & B10000000) > 0, 495 | BlocksToFollow: (data[8] & B01111111), 496 | Resync: (data[9] & B10000000) > 0, 497 | SendSequenceNumber: (data[9] & B01110000) >> 4, 498 | FragmentSequenceNumber: (data[9] & B00001111), 499 | } 500 | break 501 | 502 | case PacketFormatShortDataRaw: 503 | h.Data = &ShortDataRawData{ 504 | AppendedBlocks: (data[0] & B00110000) | (data[1] & B00001111), 505 | SrcPort: (data[8] & B11100000) >> 5, 506 | DstPort: (data[8] & B00011100) >> 2, 507 | Resync: (data[8] & B00000010) > 0, 508 | FullMessage: (data[8] & B00000001) > 0, 509 | BitPadding: (data[9]), 510 | } 511 | break 512 | 513 | case PacketFormatShortDataDefined: 514 | h.Data = &ShortDataDefinedData{ 515 | AppendedBlocks: (data[0] & B00110000) | (data[1] & B00001111), 516 | DDFormat: (data[8] & B11111100) >> 2, 517 | Resync: (data[8] & B00000010) > 0, 518 | FullMessage: (data[8] & B00000001) > 0, 519 | BitPadding: (data[9]), 520 | } 521 | break 522 | 523 | default: 524 | return nil, fmt.Errorf("dmr: unknown data data packet format %#02x (%d)", h.PacketFormat, h.PacketFormat) 525 | } 526 | } 527 | 528 | return h, nil 529 | } 530 | 531 | func dataHeaderCRC(data []byte) uint16 { 532 | var crc uint16 533 | if len(data) < 10 { 534 | return crc 535 | } 536 | 537 | for i := 0; i < 10; i++ { 538 | crc16(&crc, data[i]) 539 | } 540 | crc16end(&crc) 541 | 542 | return (^crc) ^ 0xcccc 543 | } 544 | -------------------------------------------------------------------------------- /dataheader_test.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | ) 7 | 8 | func testDataHeader(want *DataHeader, t *testing.T) *DataHeader { 9 | want.SrcID = 2042214 10 | want.DstID = 2043044 11 | 12 | t.Logf("encode:\n%s", want.String()) 13 | 14 | data, err := want.Bytes() 15 | if err != nil { 16 | t.Fatalf("encode failed: %v", err) 17 | } 18 | t.Logf("encoded:\n%s", hex.Dump(data)) 19 | 20 | test, err := ParseDataHeader(data, false) 21 | if err != nil { 22 | t.Fatalf("decode failed: %v", err) 23 | } 24 | if test.SrcID != want.SrcID || test.DstID != want.DstID { 25 | t.Fatal("decode failed, ID wrong") 26 | } 27 | t.Logf("decoded:\n%s", test.String()) 28 | 29 | return test 30 | } 31 | 32 | func TestDataHeaderUDT(t *testing.T) { 33 | want := &DataHeader{ 34 | PacketFormat: PacketFormatUDT, 35 | Data: &UDTData{ 36 | Format: UDTFormatIPAddress, 37 | PadNibble: 2, 38 | AppendedBlocks: 3, 39 | Opcode: 4, 40 | }, 41 | } 42 | test := testDataHeader(want, t) 43 | 44 | d, ok := test.Data.(*UDTData) 45 | switch { 46 | case !ok: 47 | t.Fatalf("decode failed: expected UDTData, got %T", test.Data) 48 | 49 | case d.Format != UDTFormatIPAddress: 50 | t.Fatalf("decode failed: format wrong") 51 | 52 | case d.PadNibble != 2: 53 | t.Fatalf("decode failed: pad nibble wrong") 54 | 55 | case d.AppendedBlocks != 3: 56 | t.Fatalf("decode failed: appended blocks wrong") 57 | 58 | case d.Opcode != 4: 59 | t.Fatalf("decode failed: opcode wrong") 60 | } 61 | } 62 | 63 | func TestDataHeaderResponse(t *testing.T) { 64 | want := &DataHeader{ 65 | PacketFormat: PacketFormatResponse, 66 | Data: &ResponseData{ 67 | BlocksToFollow: 0x10, 68 | ClassType: ResponseTypeSelectiveACK, 69 | }, 70 | } 71 | test := testDataHeader(want, t) 72 | 73 | d, ok := test.Data.(*ResponseData) 74 | switch { 75 | case !ok: 76 | t.Fatalf("decode failed: expected ResponseData, got %T", test.Data) 77 | 78 | case d.BlocksToFollow != 0x10: 79 | t.Fatalf("decode failed: wrong blocks %d, expected 16", d.BlocksToFollow) 80 | 81 | case d.ClassType != ResponseTypeSelectiveACK: 82 | t.Fatalf("decode failed: wrong type %s (%d), expected selective ACK", ResponseTypeName[d.ClassType], d.ClassType) 83 | } 84 | } 85 | 86 | func TestDataHeaderUnconfirmedData(t *testing.T) { 87 | want := &DataHeader{ 88 | PacketFormat: PacketFormatUnconfirmedData, 89 | Data: &UnconfirmedData{ 90 | PadOctetCount: 2, 91 | FullMessage: true, 92 | BlocksToFollow: 5, 93 | FragmentSequenceNumber: 3, 94 | }, 95 | } 96 | test := testDataHeader(want, t) 97 | 98 | d, ok := test.Data.(*UnconfirmedData) 99 | switch { 100 | case !ok: 101 | t.Fatalf("decode failed: expected UnconfirmedData, got %T", test.Data) 102 | 103 | case d.PadOctetCount != 2: 104 | t.Fatalf("decode failed: pad octet count wrong") 105 | 106 | case !d.FullMessage: 107 | t.Fatalf("decode failed: full message bit wrong") 108 | 109 | case d.BlocksToFollow != 5: 110 | t.Fatalf("decode failed: blocks to follow wrong") 111 | 112 | case d.FragmentSequenceNumber != 3: 113 | t.Fatalf("decode failed: fragment sequence number wrong") 114 | } 115 | } 116 | 117 | func TestDataHeaderConfirmedData(t *testing.T) { 118 | want := &DataHeader{ 119 | PacketFormat: PacketFormatConfirmedData, 120 | Data: &ConfirmedData{ 121 | PadOctetCount: 2, 122 | FullMessage: true, 123 | BlocksToFollow: 5, 124 | SendSequenceNumber: 4, 125 | FragmentSequenceNumber: 3, 126 | }, 127 | } 128 | test := testDataHeader(want, t) 129 | 130 | d, ok := test.Data.(*ConfirmedData) 131 | switch { 132 | case !ok: 133 | t.Fatalf("decode failed: expected ConfirmedData, got %T", test.Data) 134 | 135 | case d.PadOctetCount != 2: 136 | t.Fatalf("decode failed: pad octet count wrong") 137 | 138 | case !d.FullMessage: 139 | t.Fatalf("decode failed: full message bit wrong") 140 | 141 | case d.Resync: 142 | t.Fatalf("decode failed: resync bit wrong") 143 | 144 | case d.BlocksToFollow != 5: 145 | t.Fatalf("decode failed: blocks to follow wrong") 146 | 147 | case d.SendSequenceNumber != 4: 148 | t.Fatalf("decode failed: fragment sequence number wrong") 149 | 150 | case d.FragmentSequenceNumber != 3: 151 | t.Fatalf("decode failed: fragment sequence number wrong") 152 | } 153 | } 154 | 155 | func TestDataHeaderShortDataRaw(t *testing.T) { 156 | want := &DataHeader{ 157 | PacketFormat: PacketFormatShortDataRaw, 158 | Data: &ShortDataRawData{ 159 | AppendedBlocks: 3, 160 | SrcPort: 4, 161 | DstPort: 5, 162 | FullMessage: true, 163 | BitPadding: 2, 164 | }, 165 | } 166 | test := testDataHeader(want, t) 167 | 168 | d, ok := test.Data.(*ShortDataRawData) 169 | switch { 170 | case !ok: 171 | t.Fatalf("decode failed: expected ShortDataRawData, got %T", test.Data) 172 | 173 | case d.AppendedBlocks != 3: 174 | t.Fatalf("decode failed: appended blocks wrong") 175 | 176 | case d.SrcPort != 4: 177 | t.Fatalf("decode failed: src port wrong") 178 | 179 | case d.DstPort != 5: 180 | t.Fatalf("decode failed: dst port wrong") 181 | 182 | case d.Resync: 183 | t.Fatalf("decode failed: rsync bit wrong") 184 | 185 | case !d.FullMessage: 186 | t.Fatalf("decode failed: full message bit wrong") 187 | 188 | case d.BitPadding != 2: 189 | t.Fatalf("decode failed: bit padding wrong") 190 | } 191 | } 192 | 193 | func TestDataHeaderShortDataDefined(t *testing.T) { 194 | want := &DataHeader{ 195 | PacketFormat: PacketFormatShortDataDefined, 196 | Data: &ShortDataDefinedData{ 197 | AppendedBlocks: 3, 198 | DDFormat: DDFormatUTF16, 199 | FullMessage: true, 200 | BitPadding: 2, 201 | }, 202 | } 203 | test := testDataHeader(want, t) 204 | 205 | d, ok := test.Data.(*ShortDataDefinedData) 206 | switch { 207 | case !ok: 208 | t.Fatalf("decode failed: expected ShortDataDefinedData, got %T", test.Data) 209 | 210 | case d.AppendedBlocks != 3: 211 | t.Fatalf("decode failed: appended blocks wrong") 212 | 213 | case d.DDFormat != DDFormatUTF16: 214 | t.Fatalf("decode failed: dd format wrong, expected UTF-16, got %s", DDFormatName[d.DDFormat]) 215 | 216 | case d.Resync: 217 | t.Fatalf("decode failed: rsync bit wrong") 218 | 219 | case !d.FullMessage: 220 | t.Fatalf("decode failed: full message bit wrong") 221 | 222 | case d.BitPadding != 2: 223 | t.Fatalf("decode failed: bit padding wrong") 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /dmr.go: -------------------------------------------------------------------------------- 1 | // Package dmr implements various protocols for interfacing Digital Mobile Radio repeaters and base stations. 2 | package dmr 3 | 4 | import "github.com/op/go-logging" 5 | 6 | var log = logging.MustGetLogger("dmr/terminal") 7 | -------------------------------------------------------------------------------- /docs/DMRplus IPSC Protocol for HB repeater (20150726).pdf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e5f51c3e75c21287d3038c1dc84d99838509e46ac65fcfda35a8f24ea0dc50fa 3 | size 123620 4 | -------------------------------------------------------------------------------- /docs/MMDVM Specification 20151208.pdf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bb3f7a6c32c78e4b6efd1b89deae8cfc076b2e142f64003d8dbece036867dd8c 3 | size 383113 4 | -------------------------------------------------------------------------------- /docs/ts_10236101v010405p.pdf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:560f886910ee68c9d17bebb17ea4f96b297ef77ff036a0bcd149da0b2316ec8c 3 | size 1259856 4 | -------------------------------------------------------------------------------- /fec/fec.go: -------------------------------------------------------------------------------- 1 | // Package fec implementes Forward Error Correction algorithms 2 | package fec 3 | -------------------------------------------------------------------------------- /fec/golay_20_8.go: -------------------------------------------------------------------------------- 1 | package fec 2 | 3 | import "fmt" 4 | 5 | func Golay_20_8_Parity(bits []byte) []byte { 6 | var p = make([]byte, 12) 7 | p[0] = bits[1] ^ bits[4] ^ bits[5] ^ bits[6] ^ bits[7] 8 | p[1] = bits[1] ^ bits[2] ^ bits[4] 9 | p[2] = bits[0] ^ bits[2] ^ bits[3] ^ bits[5] 10 | p[3] = bits[0] ^ bits[1] ^ bits[3] ^ bits[4] ^ bits[6] 11 | p[4] = bits[0] ^ bits[1] ^ bits[2] ^ bits[4] ^ bits[5] ^ bits[7] 12 | p[5] = bits[0] ^ bits[2] ^ bits[3] ^ bits[4] ^ bits[7] 13 | p[6] = bits[3] ^ bits[6] ^ bits[7] 14 | p[7] = bits[0] ^ bits[1] ^ bits[5] ^ bits[6] 15 | p[8] = bits[0] ^ bits[1] ^ bits[2] ^ bits[6] ^ bits[7] 16 | p[9] = bits[2] ^ bits[3] ^ bits[4] ^ bits[5] ^ bits[6] 17 | p[10] = bits[0] ^ bits[3] ^ bits[4] ^ bits[5] ^ bits[6] ^ bits[7] 18 | p[11] = bits[1] ^ bits[2] ^ bits[3] ^ bits[5] ^ bits[7] 19 | return p 20 | } 21 | 22 | func Golay_20_8_Check(bits []byte) error { 23 | if len(bits) != 20 { 24 | return fmt.Errorf("fec/golay_20_8: expected 20 bits, got %d", len(bits)) 25 | } 26 | parity := Golay_20_8_Parity(bits[:8]) 27 | for i := 0; i < 20; i++ { 28 | if parity[i] != bits[8+i] { 29 | return fmt.Errorf("fec/golay_20_8: parity error at bit %d: %v != %v", i, parity, bits[8:]) 30 | } 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /fec/golay_23_12.go: -------------------------------------------------------------------------------- 1 | package fec 2 | 3 | var ( 4 | golayGenerator = [12]uint32{ 5 | 0x063a, 0x031d, 0x07b4, 0x03da, 6 | 0x01ed, 0x06cc, 0x0366, 0x01b3, 7 | 0x06e3, 0x054b, 0x049f, 0x0475, 8 | } 9 | golayMatrix = [2048]uint32{ 10 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 11 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0048, 12 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0824, 13 | 0x0000, 0x0000, 0x0000, 0x0301, 0x0000, 0x0400, 0x0090, 0x0002, 14 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0048, 15 | 0x0000, 0x0000, 0x0000, 0x0048, 0x0000, 0x0048, 0x0048, 0x0048, 16 | 0x0000, 0x0000, 0x0000, 0x0010, 0x0000, 0x0001, 0x0602, 0x0180, 17 | 0x0000, 0x0086, 0x0800, 0x0420, 0x0120, 0x0a10, 0x0005, 0x0048, 18 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0500, 19 | 0x0000, 0x0000, 0x0000, 0x0004, 0x0000, 0x0222, 0x0090, 0x0801, 20 | 0x0000, 0x0000, 0x0000, 0x0042, 0x0000, 0x0001, 0x0090, 0x0208, 21 | 0x0000, 0x0808, 0x0090, 0x0420, 0x0090, 0x0144, 0x0090, 0x0090, 22 | 0x0000, 0x0000, 0x0000, 0x0a80, 0x0000, 0x0001, 0x0020, 0x0016, 23 | 0x0000, 0x0110, 0x0003, 0x0420, 0x0c04, 0x0080, 0x0300, 0x0048, 24 | 0x0000, 0x0001, 0x010c, 0x0420, 0x0001, 0x0001, 0x0840, 0x0001, 25 | 0x0240, 0x0420, 0x0420, 0x0420, 0x000a, 0x0001, 0x0090, 0x0420, 26 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0500, 27 | 0x0000, 0x0000, 0x0000, 0x00a0, 0x0000, 0x0015, 0x0a00, 0x0002, 28 | 0x0000, 0x0000, 0x0000, 0x0010, 0x0000, 0x02c0, 0x0009, 0x0002, 29 | 0x0000, 0x0808, 0x0444, 0x0002, 0x0120, 0x0002, 0x0002, 0x0002, 30 | 0x0000, 0x0000, 0x0000, 0x0010, 0x0000, 0x0802, 0x0084, 0x0221, 31 | 0x0000, 0x0600, 0x0003, 0x0904, 0x0120, 0x0080, 0x0410, 0x0048, 32 | 0x0000, 0x0010, 0x0010, 0x0010, 0x0120, 0x040c, 0x0840, 0x0010, 33 | 0x0120, 0x0041, 0x0288, 0x0010, 0x0120, 0x0120, 0x0120, 0x0002, 34 | 0x0000, 0x0000, 0x0000, 0x0500, 0x0000, 0x0500, 0x0500, 0x0500, 35 | 0x0000, 0x0808, 0x0003, 0x0250, 0x0040, 0x0080, 0x002c, 0x0500, 36 | 0x0000, 0x0808, 0x0220, 0x0085, 0x0006, 0x0030, 0x0840, 0x0500, 37 | 0x0808, 0x0808, 0x0100, 0x0808, 0x0601, 0x0808, 0x0090, 0x0002, 38 | 0x0000, 0x0064, 0x0003, 0x0008, 0x0218, 0x0080, 0x0840, 0x0500, 39 | 0x0003, 0x0080, 0x0003, 0x0003, 0x0080, 0x0080, 0x0003, 0x0080, 40 | 0x0480, 0x0302, 0x0840, 0x0010, 0x0840, 0x0001, 0x0840, 0x0840, 41 | 0x0014, 0x0808, 0x0003, 0x0420, 0x0120, 0x0080, 0x0840, 0x0204, 42 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0083, 43 | 0x0000, 0x0000, 0x0000, 0x0004, 0x0000, 0x0400, 0x0a00, 0x0130, 44 | 0x0000, 0x0000, 0x0000, 0x0010, 0x0000, 0x0400, 0x0140, 0x0208, 45 | 0x0000, 0x0400, 0x002a, 0x08c0, 0x0400, 0x0400, 0x0005, 0x0400, 46 | 0x0000, 0x0000, 0x0000, 0x0010, 0x0000, 0x0304, 0x0020, 0x0c00, 47 | 0x0000, 0x0821, 0x0580, 0x0202, 0x0012, 0x0080, 0x0005, 0x0048, 48 | 0x0000, 0x0010, 0x0010, 0x0010, 0x0888, 0x0062, 0x0005, 0x0010, 49 | 0x0240, 0x0108, 0x0005, 0x0010, 0x0005, 0x0400, 0x0005, 0x0005, 50 | 0x0000, 0x0000, 0x0000, 0x0004, 0x0000, 0x0850, 0x0020, 0x0208, 51 | 0x0000, 0x0004, 0x0004, 0x0004, 0x0109, 0x0080, 0x0442, 0x0004, 52 | 0x0000, 0x01a0, 0x0c01, 0x0208, 0x0006, 0x0208, 0x0208, 0x0208, 53 | 0x0240, 0x0013, 0x0100, 0x0004, 0x0820, 0x0400, 0x0090, 0x0208, 54 | 0x0000, 0x040a, 0x0020, 0x0141, 0x0020, 0x0080, 0x0020, 0x0020, 55 | 0x0240, 0x0080, 0x0818, 0x0004, 0x0080, 0x0080, 0x0020, 0x0080, 56 | 0x0240, 0x0804, 0x0082, 0x0010, 0x0510, 0x0001, 0x0020, 0x0208, 57 | 0x0240, 0x0240, 0x0240, 0x0420, 0x0240, 0x0080, 0x0005, 0x0902, 58 | 0x0000, 0x0000, 0x0000, 0x0010, 0x0000, 0x0028, 0x0a00, 0x0044, 59 | 0x0000, 0x0142, 0x0a00, 0x0409, 0x0a00, 0x0080, 0x0a00, 0x0a00, 60 | 0x0000, 0x0010, 0x0010, 0x0010, 0x0006, 0x0901, 0x04a0, 0x0010, 61 | 0x0081, 0x0224, 0x0100, 0x0010, 0x0058, 0x0400, 0x0a00, 0x0002, 62 | 0x0000, 0x0010, 0x0010, 0x0010, 0x0441, 0x0080, 0x010a, 0x0010, 63 | 0x000c, 0x0080, 0x0060, 0x0010, 0x0080, 0x0080, 0x0a00, 0x0080, 64 | 0x0010, 0x0010, 0x0010, 0x0010, 0x0200, 0x0010, 0x0010, 0x0010, 65 | 0x0c02, 0x0010, 0x0010, 0x0010, 0x0120, 0x0080, 0x0005, 0x0010, 66 | 0x0000, 0x0201, 0x00c8, 0x0822, 0x0006, 0x0080, 0x0011, 0x0500, 67 | 0x0430, 0x0080, 0x0100, 0x0004, 0x0080, 0x0080, 0x0a00, 0x0080, 68 | 0x0006, 0x0440, 0x0100, 0x0010, 0x0006, 0x0006, 0x0006, 0x0208, 69 | 0x0100, 0x0808, 0x0100, 0x0100, 0x0006, 0x0080, 0x0100, 0x0061, 70 | 0x0900, 0x0080, 0x0604, 0x0010, 0x0080, 0x0080, 0x0020, 0x0080, 71 | 0x0080, 0x0080, 0x0003, 0x0080, 0x0080, 0x0080, 0x0080, 0x0080, 72 | 0x0029, 0x0010, 0x0010, 0x0010, 0x0006, 0x0080, 0x0840, 0x0010, 73 | 0x0240, 0x0080, 0x0100, 0x0010, 0x0080, 0x0080, 0x0408, 0x0080, 74 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0210, 75 | 0x0000, 0x0000, 0x0000, 0x00a0, 0x0000, 0x0400, 0x0106, 0x0801, 76 | 0x0000, 0x0000, 0x0000, 0x0042, 0x0000, 0x0400, 0x0009, 0x0180, 77 | 0x0000, 0x0400, 0x0800, 0x001c, 0x0400, 0x0400, 0x0260, 0x0400, 78 | 0x0000, 0x0000, 0x0000, 0x0405, 0x0000, 0x0802, 0x0020, 0x0180, 79 | 0x0000, 0x0110, 0x0800, 0x0202, 0x0281, 0x0024, 0x0410, 0x0048, 80 | 0x0000, 0x0228, 0x0800, 0x0180, 0x0054, 0x0180, 0x0180, 0x0180, 81 | 0x0800, 0x0041, 0x0800, 0x0800, 0x000a, 0x0400, 0x0800, 0x0180, 82 | 0x0000, 0x0000, 0x0000, 0x0042, 0x0000, 0x008c, 0x0020, 0x0801, 83 | 0x0000, 0x0110, 0x0608, 0x0801, 0x0040, 0x0801, 0x0801, 0x0801, 84 | 0x0000, 0x0042, 0x0042, 0x0042, 0x0b00, 0x0030, 0x0404, 0x0042, 85 | 0x0025, 0x0280, 0x0100, 0x0042, 0x000a, 0x0400, 0x0090, 0x0801, 86 | 0x0000, 0x0110, 0x0020, 0x0008, 0x0020, 0x0640, 0x0020, 0x0020, 87 | 0x0110, 0x0110, 0x00c4, 0x0110, 0x000a, 0x0110, 0x0020, 0x0801, 88 | 0x0480, 0x0804, 0x0211, 0x0042, 0x000a, 0x0001, 0x0020, 0x0180, 89 | 0x000a, 0x0110, 0x0800, 0x0420, 0x000a, 0x000a, 0x000a, 0x0204, 90 | 0x0000, 0x0000, 0x0000, 0x00a0, 0x0000, 0x0802, 0x0009, 0x0044, 91 | 0x0000, 0x00a0, 0x00a0, 0x00a0, 0x0040, 0x0308, 0x0410, 0x00a0, 92 | 0x0000, 0x0104, 0x0009, 0x0e00, 0x0009, 0x0030, 0x0009, 0x0009, 93 | 0x0212, 0x0041, 0x0100, 0x00a0, 0x0884, 0x0400, 0x0009, 0x0002, 94 | 0x0000, 0x0802, 0x0340, 0x0008, 0x0802, 0x0802, 0x0410, 0x0802, 95 | 0x000c, 0x0041, 0x0410, 0x00a0, 0x0410, 0x0802, 0x0410, 0x0410, 96 | 0x0480, 0x0041, 0x0026, 0x0010, 0x0200, 0x0802, 0x0009, 0x0180, 97 | 0x0041, 0x0041, 0x0800, 0x0041, 0x0120, 0x0041, 0x0410, 0x0204, 98 | 0x0000, 0x0201, 0x0814, 0x0008, 0x0040, 0x0030, 0x0282, 0x0500, 99 | 0x0040, 0x0406, 0x0100, 0x00a0, 0x0040, 0x0040, 0x0040, 0x0801, 100 | 0x0480, 0x0030, 0x0100, 0x0042, 0x0030, 0x0030, 0x0009, 0x0030, 101 | 0x0100, 0x0808, 0x0100, 0x0100, 0x0040, 0x0030, 0x0100, 0x0204, 102 | 0x0480, 0x0008, 0x0008, 0x0008, 0x0105, 0x0802, 0x0020, 0x0008, 103 | 0x0a20, 0x0110, 0x0003, 0x0008, 0x0040, 0x0080, 0x0410, 0x0204, 104 | 0x0480, 0x0480, 0x0480, 0x0008, 0x0480, 0x0030, 0x0840, 0x0204, 105 | 0x0480, 0x0041, 0x0100, 0x0204, 0x000a, 0x0204, 0x0204, 0x0204, 106 | 0x0000, 0x0000, 0x0000, 0x0908, 0x0000, 0x0400, 0x0020, 0x0044, 107 | 0x0000, 0x0400, 0x0051, 0x0202, 0x0400, 0x0400, 0x0088, 0x0400, 108 | 0x0000, 0x0400, 0x0284, 0x0021, 0x0400, 0x0400, 0x0812, 0x0400, 109 | 0x0400, 0x0400, 0x0100, 0x0400, 0x0400, 0x0400, 0x0400, 0x0400, 110 | 0x0000, 0x00c0, 0x0020, 0x0202, 0x0020, 0x0019, 0x0020, 0x0020, 111 | 0x000c, 0x0202, 0x0202, 0x0202, 0x0940, 0x0400, 0x0020, 0x0202, 112 | 0x0103, 0x0804, 0x0448, 0x0010, 0x0200, 0x0400, 0x0020, 0x0180, 113 | 0x00b0, 0x0400, 0x0800, 0x0202, 0x0400, 0x0400, 0x0005, 0x0400, 114 | 0x0000, 0x0201, 0x0020, 0x0490, 0x0020, 0x0102, 0x0020, 0x0020, 115 | 0x0882, 0x0068, 0x0100, 0x0004, 0x0214, 0x0400, 0x0020, 0x0801, 116 | 0x0018, 0x0804, 0x0100, 0x0042, 0x00c1, 0x0400, 0x0020, 0x0208, 117 | 0x0100, 0x0400, 0x0100, 0x0100, 0x0400, 0x0400, 0x0100, 0x0400, 118 | 0x0020, 0x0804, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 119 | 0x0401, 0x0110, 0x0020, 0x0202, 0x0020, 0x0080, 0x0020, 0x0020, 120 | 0x0804, 0x0804, 0x0020, 0x0804, 0x0020, 0x0804, 0x0020, 0x0020, 121 | 0x0240, 0x0804, 0x0100, 0x0089, 0x000a, 0x0400, 0x0020, 0x0050, 122 | 0x0000, 0x0201, 0x0402, 0x0044, 0x0190, 0x0044, 0x0044, 0x0044, 123 | 0x000c, 0x0810, 0x0100, 0x00a0, 0x0023, 0x0400, 0x0a00, 0x0044, 124 | 0x0860, 0x008a, 0x0100, 0x0010, 0x0200, 0x0400, 0x0009, 0x0044, 125 | 0x0100, 0x0400, 0x0100, 0x0100, 0x0400, 0x0400, 0x0100, 0x0400, 126 | 0x000c, 0x0520, 0x0881, 0x0010, 0x0200, 0x0802, 0x0020, 0x0044, 127 | 0x000c, 0x000c, 0x000c, 0x0202, 0x000c, 0x0080, 0x0410, 0x0101, 128 | 0x0200, 0x0010, 0x0010, 0x0010, 0x0200, 0x0200, 0x0200, 0x0010, 129 | 0x000c, 0x0041, 0x0100, 0x0010, 0x0200, 0x0400, 0x00c2, 0x0828, 130 | 0x0201, 0x0201, 0x0100, 0x0201, 0x0c08, 0x0201, 0x0020, 0x0044, 131 | 0x0100, 0x0201, 0x0100, 0x0100, 0x0040, 0x0080, 0x0100, 0x001a, 132 | 0x0100, 0x0201, 0x0100, 0x0100, 0x0006, 0x0030, 0x0100, 0x0880, 133 | 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0400, 0x0100, 0x0100, 134 | 0x0052, 0x0201, 0x0020, 0x0008, 0x0020, 0x0080, 0x0020, 0x0020, 135 | 0x000c, 0x0080, 0x0100, 0x0c40, 0x0080, 0x0080, 0x0020, 0x0080, 136 | 0x0480, 0x0804, 0x0100, 0x0010, 0x0200, 0x0148, 0x0020, 0x0403, 137 | 0x0100, 0x0022, 0x0100, 0x0100, 0x0811, 0x0080, 0x0100, 0x0204, 138 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0210, 139 | 0x0000, 0x0000, 0x0000, 0x0004, 0x0000, 0x0980, 0x0421, 0x0002, 140 | 0x0000, 0x0000, 0x0000, 0x0488, 0x0000, 0x0001, 0x0140, 0x0002, 141 | 0x0000, 0x0070, 0x0800, 0x0002, 0x020c, 0x0002, 0x0002, 0x0002, 142 | 0x0000, 0x0000, 0x0000, 0x0122, 0x0000, 0x0001, 0x0084, 0x0c00, 143 | 0x0000, 0x0600, 0x0800, 0x0091, 0x0012, 0x0024, 0x0300, 0x0048, 144 | 0x0000, 0x0001, 0x0800, 0x0244, 0x0001, 0x0001, 0x0038, 0x0001, 145 | 0x0800, 0x0108, 0x0800, 0x0800, 0x04c0, 0x0001, 0x0800, 0x0002, 146 | 0x0000, 0x0000, 0x0000, 0x0004, 0x0000, 0x0001, 0x080a, 0x00e0, 147 | 0x0000, 0x0004, 0x0004, 0x0004, 0x0040, 0x0418, 0x0300, 0x0004, 148 | 0x0000, 0x0001, 0x0220, 0x0910, 0x0001, 0x0001, 0x0404, 0x0001, 149 | 0x0502, 0x0280, 0x0049, 0x0004, 0x0820, 0x0001, 0x0090, 0x0002, 150 | 0x0000, 0x0001, 0x0450, 0x0008, 0x0001, 0x0001, 0x0300, 0x0001, 151 | 0x00a8, 0x0842, 0x0300, 0x0004, 0x0300, 0x0001, 0x0300, 0x0300, 152 | 0x0001, 0x0001, 0x0082, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 153 | 0x0014, 0x0001, 0x0800, 0x0420, 0x0001, 0x0001, 0x0300, 0x0001, 154 | 0x0000, 0x0000, 0x0000, 0x0841, 0x0000, 0x0028, 0x0084, 0x0002, 155 | 0x0000, 0x0600, 0x0118, 0x0002, 0x0040, 0x0002, 0x0002, 0x0002, 156 | 0x0000, 0x0104, 0x0220, 0x0002, 0x0c10, 0x0002, 0x0002, 0x0002, 157 | 0x0081, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 158 | 0x0000, 0x0600, 0x0084, 0x0008, 0x0084, 0x0150, 0x0084, 0x0084, 159 | 0x0600, 0x0600, 0x0060, 0x0600, 0x0809, 0x0600, 0x0084, 0x0002, 160 | 0x004a, 0x08a0, 0x0501, 0x0010, 0x0200, 0x0001, 0x0084, 0x0002, 161 | 0x0014, 0x0600, 0x0800, 0x0002, 0x0120, 0x0002, 0x0002, 0x0002, 162 | 0x0000, 0x0092, 0x0220, 0x0008, 0x0040, 0x0a04, 0x0011, 0x0500, 163 | 0x0040, 0x0121, 0x0c80, 0x0004, 0x0040, 0x0040, 0x0040, 0x0002, 164 | 0x0220, 0x0440, 0x0220, 0x0220, 0x0188, 0x0001, 0x0220, 0x0002, 165 | 0x0014, 0x0808, 0x0220, 0x0002, 0x0040, 0x0002, 0x0002, 0x0002, 166 | 0x0900, 0x0008, 0x0008, 0x0008, 0x0422, 0x0001, 0x0084, 0x0008, 167 | 0x0014, 0x0600, 0x0003, 0x0008, 0x0040, 0x0080, 0x0300, 0x0830, 168 | 0x0014, 0x0001, 0x0220, 0x0008, 0x0001, 0x0001, 0x0840, 0x0001, 169 | 0x0014, 0x0014, 0x0014, 0x01c0, 0x0014, 0x0001, 0x0408, 0x0002, 170 | 0x0000, 0x0000, 0x0000, 0x0004, 0x0000, 0x0028, 0x0140, 0x0c00, 171 | 0x0000, 0x0004, 0x0004, 0x0004, 0x0012, 0x0241, 0x0088, 0x0004, 172 | 0x0000, 0x0a02, 0x0140, 0x0021, 0x0140, 0x0094, 0x0140, 0x0140, 173 | 0x0081, 0x0108, 0x0610, 0x0004, 0x0820, 0x0400, 0x0140, 0x0002, 174 | 0x0000, 0x00c0, 0x0209, 0x0c00, 0x0012, 0x0c00, 0x0c00, 0x0c00, 175 | 0x0012, 0x0108, 0x0060, 0x0004, 0x0012, 0x0012, 0x0012, 0x0c00, 176 | 0x0424, 0x0108, 0x0082, 0x0010, 0x0200, 0x0001, 0x0140, 0x0c00, 177 | 0x0108, 0x0108, 0x0800, 0x0108, 0x0012, 0x0108, 0x0005, 0x02a0, 178 | 0x0000, 0x0004, 0x0004, 0x0004, 0x0680, 0x0102, 0x0011, 0x0004, 179 | 0x0004, 0x0004, 0x0004, 0x0004, 0x0820, 0x0004, 0x0004, 0x0004, 180 | 0x0018, 0x0440, 0x0082, 0x0004, 0x0820, 0x0001, 0x0140, 0x0208, 181 | 0x0820, 0x0004, 0x0004, 0x0004, 0x0820, 0x0820, 0x0820, 0x0004, 182 | 0x0900, 0x0230, 0x0082, 0x0004, 0x004c, 0x0001, 0x0020, 0x0c00, 183 | 0x0401, 0x0004, 0x0004, 0x0004, 0x0012, 0x0080, 0x0300, 0x0004, 184 | 0x0082, 0x0001, 0x0082, 0x0082, 0x0001, 0x0001, 0x0082, 0x0001, 185 | 0x0240, 0x0108, 0x0082, 0x0004, 0x0820, 0x0001, 0x0408, 0x0050, 186 | 0x0000, 0x0028, 0x0402, 0x0380, 0x0028, 0x0028, 0x0011, 0x0028, 187 | 0x0081, 0x0810, 0x0060, 0x0004, 0x0504, 0x0028, 0x0a00, 0x0002, 188 | 0x0081, 0x0440, 0x080c, 0x0010, 0x0200, 0x0028, 0x0140, 0x0002, 189 | 0x0081, 0x0081, 0x0081, 0x0002, 0x0081, 0x0002, 0x0002, 0x0002, 190 | 0x0900, 0x0007, 0x0060, 0x0010, 0x0200, 0x0028, 0x0084, 0x0c00, 191 | 0x0060, 0x0600, 0x0060, 0x0060, 0x0012, 0x0080, 0x0060, 0x0101, 192 | 0x0200, 0x0010, 0x0010, 0x0010, 0x0200, 0x0200, 0x0200, 0x0010, 193 | 0x0081, 0x0108, 0x0060, 0x0010, 0x0200, 0x0844, 0x0408, 0x0002, 194 | 0x0900, 0x0440, 0x0011, 0x0004, 0x0011, 0x0028, 0x0011, 0x0011, 195 | 0x020a, 0x0004, 0x0004, 0x0004, 0x0040, 0x0080, 0x0011, 0x0004, 196 | 0x0440, 0x0440, 0x0220, 0x0440, 0x0006, 0x0440, 0x0011, 0x0880, 197 | 0x0081, 0x0440, 0x0100, 0x0004, 0x0820, 0x0310, 0x0408, 0x0002, 198 | 0x0900, 0x0900, 0x0900, 0x0008, 0x0900, 0x0080, 0x0011, 0x0242, 199 | 0x0900, 0x0080, 0x0060, 0x0004, 0x0080, 0x0080, 0x0408, 0x0080, 200 | 0x0900, 0x0440, 0x0082, 0x0010, 0x0200, 0x0001, 0x0408, 0x0124, 201 | 0x0014, 0x0022, 0x0408, 0x0a01, 0x0408, 0x0080, 0x0408, 0x0408, 202 | 0x0000, 0x0000, 0x0000, 0x0210, 0x0000, 0x0210, 0x0210, 0x0210, 203 | 0x0000, 0x000b, 0x0800, 0x0540, 0x0040, 0x0024, 0x0088, 0x0210, 204 | 0x0000, 0x0104, 0x0800, 0x0021, 0x00a2, 0x0848, 0x0404, 0x0210, 205 | 0x0800, 0x0280, 0x0800, 0x0800, 0x0111, 0x0400, 0x0800, 0x0002, 206 | 0x0000, 0x00c0, 0x0800, 0x0008, 0x0508, 0x0024, 0x0043, 0x0210, 207 | 0x0800, 0x0024, 0x0800, 0x0800, 0x0024, 0x0024, 0x0800, 0x0024, 208 | 0x0800, 0x0412, 0x0800, 0x0800, 0x0200, 0x0001, 0x0800, 0x0180, 209 | 0x0800, 0x0800, 0x0800, 0x0800, 0x0800, 0x0024, 0x0800, 0x0800, 210 | 0x0000, 0x0c20, 0x0181, 0x0008, 0x0040, 0x0102, 0x0404, 0x0210, 211 | 0x0040, 0x0280, 0x0032, 0x0004, 0x0040, 0x0040, 0x0040, 0x0801, 212 | 0x0018, 0x0280, 0x0404, 0x0042, 0x0404, 0x0001, 0x0404, 0x0404, 213 | 0x0280, 0x0280, 0x0800, 0x0280, 0x0040, 0x0280, 0x0404, 0x0128, 214 | 0x0206, 0x0008, 0x0008, 0x0008, 0x0890, 0x0001, 0x0020, 0x0008, 215 | 0x0401, 0x0110, 0x0800, 0x0008, 0x0040, 0x0024, 0x0300, 0x0482, 216 | 0x0160, 0x0001, 0x0800, 0x0008, 0x0001, 0x0001, 0x0404, 0x0001, 217 | 0x0800, 0x0280, 0x0800, 0x0800, 0x000a, 0x0001, 0x0800, 0x0050, 218 | 0x0000, 0x0104, 0x0402, 0x0008, 0x0040, 0x0481, 0x0920, 0x0210, 219 | 0x0040, 0x0810, 0x0205, 0x00a0, 0x0040, 0x0040, 0x0040, 0x0002, 220 | 0x0104, 0x0104, 0x00d0, 0x0104, 0x0200, 0x0104, 0x0009, 0x0002, 221 | 0x0428, 0x0104, 0x0800, 0x0002, 0x0040, 0x0002, 0x0002, 0x0002, 222 | 0x0031, 0x0008, 0x0008, 0x0008, 0x0200, 0x0802, 0x0084, 0x0008, 223 | 0x0182, 0x0600, 0x0800, 0x0008, 0x0040, 0x0024, 0x0410, 0x0101, 224 | 0x0200, 0x0104, 0x0800, 0x0008, 0x0200, 0x0200, 0x0200, 0x0460, 225 | 0x0800, 0x0041, 0x0800, 0x0800, 0x0200, 0x0098, 0x0800, 0x0002, 226 | 0x0040, 0x0008, 0x0008, 0x0008, 0x0040, 0x0040, 0x0040, 0x0008, 227 | 0x0040, 0x0040, 0x0040, 0x0008, 0x0040, 0x0040, 0x0040, 0x0040, 228 | 0x0803, 0x0104, 0x0220, 0x0008, 0x0040, 0x0030, 0x0404, 0x0880, 229 | 0x0040, 0x0280, 0x0100, 0x0411, 0x0040, 0x0040, 0x0040, 0x0002, 230 | 0x0008, 0x0008, 0x0008, 0x0008, 0x0040, 0x0008, 0x0008, 0x0008, 231 | 0x0040, 0x0008, 0x0008, 0x0008, 0x0040, 0x0040, 0x0040, 0x0008, 232 | 0x0480, 0x0008, 0x0008, 0x0008, 0x0200, 0x0001, 0x0112, 0x0008, 233 | 0x0014, 0x0022, 0x0800, 0x0008, 0x0040, 0x0d00, 0x00a1, 0x0204, 234 | 0x0000, 0x00c0, 0x0402, 0x0021, 0x0805, 0x0102, 0x0088, 0x0210, 235 | 0x0320, 0x0810, 0x0088, 0x0004, 0x0088, 0x0400, 0x0088, 0x0088, 236 | 0x0018, 0x0021, 0x0021, 0x0021, 0x0200, 0x0400, 0x0140, 0x0021, 237 | 0x0046, 0x0400, 0x0800, 0x0021, 0x0400, 0x0400, 0x0088, 0x0400, 238 | 0x00c0, 0x00c0, 0x0114, 0x00c0, 0x0200, 0x00c0, 0x0020, 0x0c00, 239 | 0x0401, 0x00c0, 0x0800, 0x0202, 0x0012, 0x0024, 0x0088, 0x0101, 240 | 0x0200, 0x00c0, 0x0800, 0x0021, 0x0200, 0x0200, 0x0200, 0x000e, 241 | 0x0800, 0x0108, 0x0800, 0x0800, 0x0200, 0x0400, 0x0800, 0x0050, 242 | 0x0018, 0x0102, 0x0a40, 0x0004, 0x0102, 0x0102, 0x0020, 0x0102, 243 | 0x0401, 0x0004, 0x0004, 0x0004, 0x0040, 0x0102, 0x0088, 0x0004, 244 | 0x0018, 0x0018, 0x0018, 0x0021, 0x0018, 0x0102, 0x0404, 0x0880, 245 | 0x0018, 0x0280, 0x0100, 0x0004, 0x0820, 0x0400, 0x0203, 0x0050, 246 | 0x0401, 0x00c0, 0x0020, 0x0008, 0x0020, 0x0102, 0x0020, 0x0020, 247 | 0x0401, 0x0401, 0x0401, 0x0004, 0x0401, 0x0a08, 0x0020, 0x0050, 248 | 0x0018, 0x0804, 0x0082, 0x0700, 0x0200, 0x0001, 0x0020, 0x0050, 249 | 0x0401, 0x0022, 0x0800, 0x0050, 0x0184, 0x0050, 0x0050, 0x0050, 250 | 0x0402, 0x0810, 0x0402, 0x0402, 0x0200, 0x0028, 0x0402, 0x0044, 251 | 0x0810, 0x0810, 0x0402, 0x0810, 0x0040, 0x0810, 0x0088, 0x0101, 252 | 0x0200, 0x0104, 0x0402, 0x0021, 0x0200, 0x0200, 0x0200, 0x0880, 253 | 0x0081, 0x0810, 0x0100, 0x0248, 0x0200, 0x0400, 0x0034, 0x0002, 254 | 0x0200, 0x00c0, 0x0402, 0x0008, 0x0200, 0x0200, 0x0200, 0x0101, 255 | 0x000c, 0x0810, 0x0060, 0x0101, 0x0200, 0x0101, 0x0101, 0x0101, 256 | 0x0200, 0x0200, 0x0200, 0x0010, 0x0200, 0x0200, 0x0200, 0x0200, 257 | 0x0200, 0x0022, 0x0800, 0x0484, 0x0200, 0x0200, 0x0200, 0x0101, 258 | 0x00a4, 0x0201, 0x0402, 0x0008, 0x0040, 0x0102, 0x0011, 0x0880, 259 | 0x0040, 0x0810, 0x0100, 0x0004, 0x0040, 0x0040, 0x0040, 0x0620, 260 | 0x0018, 0x0440, 0x0100, 0x0880, 0x0200, 0x0880, 0x0880, 0x0880, 261 | 0x0100, 0x0022, 0x0100, 0x0100, 0x0040, 0x000d, 0x0100, 0x0880, 262 | 0x0900, 0x0008, 0x0008, 0x0008, 0x0200, 0x0414, 0x0020, 0x0008, 263 | 0x0401, 0x0022, 0x0290, 0x0008, 0x0040, 0x0080, 0x0806, 0x0101, 264 | 0x0200, 0x0022, 0x0045, 0x0008, 0x0200, 0x0200, 0x0200, 0x0880, 265 | 0x0022, 0x0022, 0x0100, 0x0022, 0x0200, 0x0022, 0x0408, 0x0050, 266 | } 267 | ) 268 | 269 | // Golay(23, 12) forward error correction. 270 | func Golay_23_12_Correct(block *uint32) { 271 | var ( 272 | mask = uint32(0x400000) 273 | curr = *block 274 | syndrome uint32 275 | ) 276 | 277 | for i := 0; i < 12; i++ { 278 | if (curr & mask) != 0 { 279 | syndrome ^= golayGenerator[i] 280 | } 281 | mask >>= 1 282 | } 283 | 284 | syndrome ^= (curr & 0x07ff) 285 | *block = ((curr >> 11) ^ golayMatrix[syndrome]) 286 | } 287 | 288 | // Golay(23, 12) codeword. 289 | func Golay_23_12_Encode(codeword uint32) uint32 { 290 | var c = codeword 291 | for i := 0; i < 12; i++ { 292 | if codeword&1 == 1 { // Test data bit 293 | codeword ^= 0xc75 // XOR polynomial 294 | codeword >>= 1 // Shift intermediate result 295 | } 296 | } 297 | return ((codeword << 12) | c) // Assemble encoded codeword 298 | } 299 | -------------------------------------------------------------------------------- /fec/hamming_15_13_3.go: -------------------------------------------------------------------------------- 1 | package fec 2 | 3 | var ( 4 | hamming15_11_3_gen = [11]uint32{ 5 | 0x4009, 0x200d, 0x100f, 0x080e, 0x0407, 0x020a, 0x0105, 0x008b, 0x004c, 0x0026, 0x0013, 6 | } 7 | hamming15_11_3_table = [16]uint32{ 8 | 0x0000, 0x0001, 0x0002, 0x0013, 0x0004, 0x0105, 0x0026, 0x0407, 9 | 0x0008, 0x4009, 0x020a, 0x008b, 0x004c, 0x200d, 0x080e, 0x100f, 10 | } 11 | ) 12 | 13 | // Correct a block of data using Hamming(15, 11, 3). 14 | func Hamming15_11_3_Correct(block *uint32) { 15 | var ( 16 | ecc, syndrome uint32 17 | codeword = *block 18 | ) 19 | 20 | for i := 0; i < 11; i++ { 21 | if (codeword & hamming15_11_3_gen[i]) > 0x0f { 22 | ecc ^= hamming15_11_3_gen[i] 23 | } 24 | } 25 | syndrome = ecc ^ codeword 26 | 27 | if syndrome != 0 { 28 | codeword ^= hamming15_11_3_table[syndrome&0x0f] 29 | } 30 | 31 | *block = (codeword >> 4) 32 | } 33 | 34 | // Hamming(15, 11, 3) encode a block of data. 35 | func Hamming15_11_3_Encode(input uint32) uint32 { 36 | var codeword uint32 37 | for i := uint8(0); i < 11; i++ { 38 | if input&(1<<(10-i)) > 0 { 39 | codeword ^= hamming15_11_3_gen[i] 40 | } 41 | } 42 | return codeword 43 | } 44 | -------------------------------------------------------------------------------- /fec/rs_12_9.go: -------------------------------------------------------------------------------- 1 | package fec 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | const ( 9 | RS_12_9_DATASIZE = 9 10 | RS_12_9_CHECKSUMSIZE = 3 11 | // Maximum degree of various polynomials 12 | RS_12_9_POLY_MAXDEG = RS_12_9_CHECKSUMSIZE * 2 13 | ) 14 | 15 | type RS_12_9_Poly [RS_12_9_POLY_MAXDEG]uint8 16 | 17 | var ( 18 | // DMR AI. spec. page 138. 19 | rs_12_9_galois_exp_table = [256]uint8{ 20 | 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 21 | 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 22 | 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 23 | 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 24 | 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 25 | 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 26 | 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 27 | 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 28 | 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 29 | 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 30 | 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 31 | 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 32 | 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 33 | 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, 34 | 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, 35 | 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01, 36 | } 37 | // DMR AI. spec. page 138. 38 | rs_12_9_galois_log_table = [256]uint8{ 39 | 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 40 | 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 41 | 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 42 | 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 43 | 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 44 | 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 45 | 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 46 | 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 47 | 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 48 | 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 49 | 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 50 | 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 51 | 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 52 | 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 53 | 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 54 | 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 55 | } 56 | ) 57 | 58 | func RS_12_9_Galois_Inv(elt uint8) uint8 { 59 | return rs_12_9_galois_exp_table[255-rs_12_9_galois_log_table[elt]] 60 | } 61 | 62 | func RS_12_9_Galois_Mul(a, b uint8) uint8 { 63 | if a == 0 || b == 0 { 64 | return 0 65 | } 66 | return rs_12_9_galois_exp_table[(rs_12_9_galois_log_table[a]+rs_12_9_galois_log_table[b])%255] 67 | } 68 | 69 | // Multiply by z (shift right by 1). 70 | func RS_12_9_MulPolyZ(poly *RS_12_9_Poly) { 71 | for i := RS_12_9_POLY_MAXDEG - 1; i > 0; i-- { 72 | poly[i] = poly[i-1] 73 | } 74 | poly[0] = 0 75 | } 76 | 77 | func RS_12_9_MulPolys(p1, p2 *RS_12_9_Poly, dst []uint8) { 78 | var ( 79 | i, j uint8 80 | tmp = make([]uint8, RS_12_9_POLY_MAXDEG*2) 81 | ) 82 | 83 | for i = 0; i < RS_12_9_POLY_MAXDEG*2; i++ { 84 | dst[i] = 0 85 | } 86 | 87 | for i = 0; i < RS_12_9_POLY_MAXDEG; i++ { 88 | for j := RS_12_9_POLY_MAXDEG; j < (RS_12_9_POLY_MAXDEG * 2); j++ { 89 | tmp[j] = 0 90 | } 91 | 92 | // Scale tmp by p1[i] 93 | for j = 0; j < RS_12_9_POLY_MAXDEG; j++ { 94 | tmp[j] = RS_12_9_Galois_Mul(p2[j], p1[i]) 95 | } 96 | 97 | // Shift (multiply) tmp right by i 98 | for j = (RS_12_9_POLY_MAXDEG * 2) - 1; j >= i && j < (RS_12_9_POLY_MAXDEG*2)-1; j-- { 99 | tmp[j] = tmp[j-i] 100 | } 101 | for j = 0; j < i; j++ { 102 | tmp[j] = 0 103 | } 104 | 105 | // Add into partial product 106 | for j = 0; j < (RS_12_9_POLY_MAXDEG * 2); j++ { 107 | dst[j] ^= tmp[j] 108 | } 109 | } 110 | } 111 | 112 | // Computes the combined erasure/error evaluator polynomial (error_locator_poly*syndrome mod z^4) 113 | func RS_12_9_CalcErrorEvaluatorPoly(locator, syndrome, evaluator *RS_12_9_Poly) { 114 | var ( 115 | i uint8 116 | product = make([]uint8, RS_12_9_POLY_MAXDEG*2) 117 | ) 118 | 119 | RS_12_9_MulPolys(locator, syndrome, product) 120 | for i = 0; i < RS_12_9_CHECKSUMSIZE; i++ { 121 | evaluator[i] = product[i] 122 | } 123 | for ; i < RS_12_9_POLY_MAXDEG; i++ { 124 | evaluator[i] = 0 125 | } 126 | } 127 | 128 | func RS_12_9_CalcDiscrepancy(locator, syndrome *RS_12_9_Poly, L, n uint8) uint8 { 129 | var i, sum uint8 130 | 131 | for i = 0; i <= L; i++ { 132 | sum ^= RS_12_9_Galois_Mul(locator[i], syndrome[n-i]) 133 | } 134 | 135 | return sum 136 | } 137 | 138 | // This finds the coefficients of the error locator polynomial, and then calculates 139 | // the error evaluator polynomial using the Berlekamp-Massey algorithm. 140 | // From Cain, Clark, "Error-Correction Coding For Digital Communications", pp. 216. 141 | func RS_12_9_Calc(syndrome, locator, evaluator *RS_12_9_Poly) { 142 | var ( 143 | n, L, L2 uint8 144 | k int8 145 | d, i uint8 146 | psi2 = make([]uint8, RS_12_9_POLY_MAXDEG) 147 | D = RS_12_9_Poly{0, 1, 0} 148 | ) 149 | 150 | k = -1 151 | for i = 0; i < RS_12_9_POLY_MAXDEG; i++ { 152 | locator[i] = 0 153 | } 154 | locator[0] = 1 155 | 156 | for n = 0; n < RS_12_9_CHECKSUMSIZE; n++ { 157 | d = RS_12_9_CalcDiscrepancy(locator, syndrome, L, n) 158 | if d != 0 { 159 | // psi2 = locator - d*D 160 | for i = 0; i < RS_12_9_POLY_MAXDEG; i++ { 161 | psi2[i] = locator[i] ^ RS_12_9_Galois_Mul(d, D[i]) 162 | } 163 | 164 | if int8(L) < int8(n)-k { 165 | L2 = uint8(int8(n) - k) 166 | k = int8(int8(n) - int8(L)) 167 | for i = 0; i < RS_12_9_POLY_MAXDEG; i++ { 168 | D[i] = RS_12_9_Galois_Mul(locator[i], RS_12_9_Galois_Inv(d)) 169 | } 170 | L = L2 171 | } 172 | 173 | // locator = psi2 174 | for i = 0; i < RS_12_9_POLY_MAXDEG; i++ { 175 | locator[i] = psi2[i] 176 | } 177 | } 178 | RS_12_9_MulPolyZ(&D) 179 | } 180 | RS_12_9_CalcErrorEvaluatorPoly(locator, syndrome, evaluator) 181 | } 182 | 183 | // The error-locator polynomial's roots are found by looking for the values of a^n where 184 | // evaluating the polynomial yields zero (evaluating rs_12_9_error_locator_poly at 185 | // successive values of alpha (Chien's search)). 186 | func RS_12_9_FindRoots(locator *RS_12_9_Poly) []uint8 { 187 | var k, r uint16 188 | roots := make([]uint8, 0) 189 | for r = 1; r < 256; r++ { 190 | var sum uint8 191 | // Evaluate locator at r 192 | for k = 0; k < RS_12_9_CHECKSUMSIZE+1; k++ { 193 | sum ^= RS_12_9_Galois_Mul(rs_12_9_galois_exp_table[(k*r)%255], locator[k]) 194 | } 195 | 196 | if sum == 0 { 197 | roots = append(roots, uint8(255-r)) 198 | } 199 | } 200 | 201 | return roots 202 | } 203 | 204 | func RS_12_9_CalcSyndrome(data []byte, syndrome *RS_12_9_Poly) error { 205 | if len(data) != RS_12_9_DATASIZE+RS_12_9_CHECKSUMSIZE { 206 | return fmt.Errorf("fec/rs_12_9: unexpected size %d, expected %d bytes", 207 | len(data), RS_12_9_DATASIZE+RS_12_9_CHECKSUMSIZE) 208 | } 209 | 210 | var i, j uint8 211 | for i = 0; i < 3; i++ { 212 | syndrome[i] = 0 213 | } 214 | 215 | for j = 0; j < 3; j++ { 216 | for i = 0; i < uint8(len(data)); i++ { 217 | syndrome[j] = data[i] ^ RS_12_9_Galois_Mul(rs_12_9_galois_exp_table[j+1], syndrome[j]) 218 | } 219 | } 220 | 221 | return nil 222 | } 223 | 224 | func RS_12_9_CheckSyndrome(syndrome *RS_12_9_Poly) bool { 225 | for _, v := range syndrome { 226 | if v != 0 { 227 | return true 228 | } 229 | } 230 | return false 231 | } 232 | 233 | func RS_12_9_Correct(data []byte, syndrome *RS_12_9_Poly) (int, error) { 234 | if len(data) != RS_12_9_DATASIZE+RS_12_9_CHECKSUMSIZE { 235 | return -1, fmt.Errorf("fec/rs_12_9: unexpected size %d, expected %d bytes", 236 | len(data), RS_12_9_DATASIZE+RS_12_9_CHECKSUMSIZE) 237 | } 238 | 239 | var ( 240 | i, j uint8 241 | errorsFound int 242 | locator = RS_12_9_Poly{} 243 | evaluator = RS_12_9_Poly{} 244 | ) 245 | RS_12_9_Calc(syndrome, &locator, &evaluator) 246 | roots := RS_12_9_FindRoots(&locator) 247 | errorsFound = len(roots) 248 | 249 | if errorsFound == 0 { 250 | return 0, nil 251 | } 252 | 253 | // Error correction is done using the error-evaluator equation on pp 207. 254 | if errorsFound > 0 && errorsFound < RS_12_9_CHECKSUMSIZE { 255 | // First check for illegal error locations. 256 | for r := 0; r < errorsFound; r++ { 257 | if roots[r] >= RS_12_9_DATASIZE+RS_12_9_CHECKSUMSIZE { 258 | return errorsFound, errors.New("fec/rs_12_9: errors can't be corrected") 259 | } 260 | } 261 | 262 | // Evaluates rs_12_9_error_evaluator_poly/rs_12_9_error_locator_poly' at the roots 263 | // alpha^(-i) for error locs i. 264 | for r := 0; r < errorsFound; r++ { 265 | i = roots[r] 266 | 267 | var num, denom uint8 268 | // Evaluate rs_12_9_error_evaluator_poly at alpha^(-i) 269 | for j = 0; j < RS_12_9_POLY_MAXDEG; j++ { 270 | num ^= RS_12_9_Galois_Mul(evaluator[j], rs_12_9_galois_exp_table[((255-i)*j)%255]) 271 | } 272 | 273 | // Evaluate rs_12_9_error_evaluator_poly' (derivative) at alpha^(-i). All odd powers disappear. 274 | for j = 1; j < RS_12_9_POLY_MAXDEG; j += 2 { 275 | denom ^= RS_12_9_Galois_Mul(locator[j], rs_12_9_galois_exp_table[((255-i)*(j-1))%255]) 276 | } 277 | 278 | data[len(data)-int(i)-1] ^= RS_12_9_Galois_Mul(num, RS_12_9_Galois_Inv(denom)) 279 | } 280 | 281 | return errorsFound, nil 282 | } 283 | 284 | return 0, nil 285 | } 286 | 287 | // Simulates an LFSR with the generator polynomial and calculates checksum bytes for the given data. 288 | func RS_12_9_CalcChecksum(data []byte) []uint8 { 289 | var ( 290 | feedback uint8 291 | genpoly = []uint8{0x40, 0x38, 0x0e, 0x01} // See DMR AI. spec. page 136 for these coefficients. 292 | checksum = make([]uint8, 3) 293 | ) 294 | 295 | for i := 0; i < 9; i++ { 296 | feedback = data[i] ^ checksum[0] 297 | checksum[0] = checksum[1] ^ RS_12_9_Galois_Mul(genpoly[2], feedback) 298 | checksum[1] = checksum[2] ^ RS_12_9_Galois_Mul(genpoly[1], feedback) 299 | checksum[2] = RS_12_9_Galois_Mul(genpoly[0], feedback) 300 | } 301 | return checksum 302 | } 303 | -------------------------------------------------------------------------------- /homebrew/config.go: -------------------------------------------------------------------------------- 1 | package homebrew 2 | 3 | type Config struct { 4 | // ID is the local DMR ID. 5 | ID uint32 6 | 7 | // PeerID is the remote DMR ID. 8 | PeerID uint32 9 | 10 | // AuthKey is the shared secret. 11 | AuthKey string 12 | } 13 | -------------------------------------------------------------------------------- /homebrew/homebrew.go: -------------------------------------------------------------------------------- 1 | // Package homebrew implements the Home Brew DMR IPSC protocol 2 | package homebrew 3 | 4 | import ( 5 | "bytes" 6 | "crypto/rand" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "errors" 10 | "fmt" 11 | "net" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | "time" 16 | 17 | "github.com/op/go-logging" 18 | "github.com/pd0mz/go-dmr" 19 | ) 20 | 21 | var log = logging.MustGetLogger("dmr/homebrew") 22 | 23 | type AuthStatus uint8 24 | 25 | func (a *AuthStatus) String() string { 26 | switch *a { 27 | case AuthNone: 28 | return "none" 29 | case AuthBegin: 30 | return "begin" 31 | case AuthDone: 32 | return "none" 33 | case AuthFailed: 34 | return "failed" 35 | default: 36 | return "invalid" 37 | } 38 | } 39 | 40 | const ( 41 | AuthNone AuthStatus = iota 42 | AuthBegin 43 | AuthDone 44 | AuthFailed 45 | ) 46 | 47 | // Messages as documented by DL5DI, G4KLX and DG1HT, see "DMRplus IPSC Protocol for HB repeater (20150726).pdf". 48 | var ( 49 | DMRData = []byte("DMRD") 50 | MasterNAK = []byte("MSTNAK") 51 | MasterACK = []byte("MSTACK") 52 | RepeaterLogin = []byte("RPTL") 53 | RepeaterKey = []byte("RPTK") 54 | MasterPing = []byte("MSTPING") 55 | RepeaterPong = []byte("RPTPONG") 56 | MasterClosing = []byte("MSTCL") 57 | RepeaterClosing = []byte("RPTCL") 58 | ) 59 | 60 | // We ping the peers every minute 61 | var ( 62 | AuthTimeout = time.Second * 5 63 | PingInterval = time.Second * 5 64 | PingTimeout = time.Second * 15 65 | SendInterval = time.Millisecond * 30 66 | ) 67 | 68 | // Homebrew is implements the Homebrew IPSC DMR Air Interface protocol 69 | type Homebrew struct { 70 | Config *RepeaterConfiguration 71 | Peer map[string]*Peer 72 | PeerID map[uint32]*Peer 73 | 74 | pf dmr.PacketFunc 75 | conn *net.UDPConn 76 | closed bool 77 | id []byte 78 | last time.Time // Record last received frame time 79 | mutex *sync.Mutex // Mutex for manipulating peer list or send queue 80 | rxtx *sync.Mutex // Mutex for when receiving data or sending data 81 | stop chan bool 82 | queue []*dmr.Packet 83 | } 84 | 85 | // New creates a new Homebrew repeater 86 | func New(config *RepeaterConfiguration, addr *net.UDPAddr) (*Homebrew, error) { 87 | var err error 88 | 89 | if config == nil { 90 | return nil, errors.New("homebrew: RepeaterConfiguration can't be nil") 91 | } 92 | if addr == nil { 93 | return nil, errors.New("homebrew: addr can't be nil") 94 | } 95 | 96 | h := &Homebrew{ 97 | Config: config, 98 | Peer: make(map[string]*Peer), 99 | PeerID: make(map[uint32]*Peer), 100 | id: packRepeaterID(config.ID), 101 | mutex: &sync.Mutex{}, 102 | rxtx: &sync.Mutex{}, 103 | queue: make([]*dmr.Packet, 0), 104 | } 105 | if h.conn, err = net.ListenUDP("udp", addr); err != nil { 106 | return nil, errors.New("homebrew: " + err.Error()) 107 | } 108 | 109 | return h, nil 110 | } 111 | 112 | func (h *Homebrew) Active() bool { 113 | return !h.closed && h.conn != nil 114 | } 115 | 116 | // Close stops the active listeners 117 | func (h *Homebrew) Close() error { 118 | h.mutex.Lock() 119 | defer h.mutex.Unlock() 120 | 121 | if !h.Active() { 122 | return nil 123 | } 124 | 125 | log.Info("closing") 126 | 127 | // Tell peers we're closing 128 | closing: 129 | for _, peer := range h.Peer { 130 | if peer.Status == AuthDone { 131 | if err := h.WriteToPeer(append(RepeaterClosing, h.id...), peer); err != nil { 132 | break closing 133 | } 134 | } 135 | } 136 | 137 | // Kill keepalive goroutine 138 | if h.stop != nil { 139 | close(h.stop) 140 | h.stop = nil 141 | } 142 | 143 | // Kill listening socket 144 | h.closed = true 145 | return h.conn.Close() 146 | } 147 | 148 | // Link establishes a new link with a peer 149 | func (h *Homebrew) Link(peer *Peer) error { 150 | if peer == nil { 151 | return errors.New("homebrew: peer can't be nil") 152 | } 153 | if peer.Addr == nil { 154 | return errors.New("homebrew: peer Addr can't be nil") 155 | } 156 | if peer.AuthKey == nil || len(peer.AuthKey) == 0 { 157 | return errors.New("homebrew: peer AuthKey can't be nil") 158 | } 159 | 160 | h.mutex.Lock() 161 | defer h.mutex.Unlock() 162 | 163 | // Reset state 164 | peer.Last.PacketSent = time.Time{} 165 | peer.Last.PacketReceived = time.Time{} 166 | peer.Last.PingSent = time.Time{} 167 | peer.Last.PongReceived = time.Time{} 168 | 169 | // Register our peer 170 | peer.id = packRepeaterID(peer.ID) 171 | h.Peer[peer.Addr.String()] = peer 172 | h.PeerID[peer.ID] = peer 173 | 174 | return h.handleAuth(peer) 175 | } 176 | 177 | func (h *Homebrew) Unlink(id uint32) error { 178 | h.mutex.Lock() 179 | defer h.mutex.Unlock() 180 | 181 | peer, ok := h.PeerID[id] 182 | if !ok { 183 | return fmt.Errorf("homebrew: peer %d not linked", id) 184 | } 185 | 186 | delete(h.Peer, peer.Addr.String()) 187 | delete(h.PeerID, id) 188 | return nil 189 | } 190 | 191 | func (h *Homebrew) ListenAndServe() error { 192 | var data = make([]byte, 53) 193 | 194 | h.stop = make(chan bool) 195 | go h.keepalive(h.stop) 196 | 197 | h.closed = false 198 | for !h.closed { 199 | n, peer, err := h.conn.ReadFromUDP(data) 200 | if err != nil { 201 | return err 202 | } 203 | if err := h.handle(peer, data[:n]); err != nil { 204 | if h.closed && strings.HasSuffix(err.Error(), "use of closed network connection") { 205 | break 206 | } 207 | return err 208 | } 209 | } 210 | 211 | log.Info("listener closed") 212 | return nil 213 | } 214 | 215 | // Send a packet to the peers. Will block until the packet is sent. 216 | func (h *Homebrew) Send(p *dmr.Packet) error { 217 | h.rxtx.Lock() 218 | defer h.rxtx.Unlock() 219 | 220 | data := BuildData(p, h.Config.ID) 221 | for _, peer := range h.getPeers() { 222 | if err := h.WriteToPeer(data, peer); err != nil { 223 | return err 224 | } 225 | } 226 | 227 | return nil 228 | } 229 | 230 | func (h *Homebrew) GetPacketFunc() dmr.PacketFunc { 231 | return h.pf 232 | } 233 | 234 | func (h *Homebrew) SetPacketFunc(f dmr.PacketFunc) { 235 | h.pf = f 236 | } 237 | 238 | func (h *Homebrew) WritePacketToPeer(p *dmr.Packet, peer *Peer) error { 239 | return h.WriteToPeer(h.parsePacket(p), peer) 240 | } 241 | 242 | func (h *Homebrew) WriteToPeer(b []byte, peer *Peer) error { 243 | if peer == nil { 244 | return errors.New("homebrew: can't write to nil peer") 245 | } 246 | 247 | peer.Last.PacketSent = time.Now() 248 | _, err := h.conn.WriteTo(b, peer.Addr) 249 | return err 250 | } 251 | 252 | func (h *Homebrew) WriteToPeerWithID(b []byte, id uint32) error { 253 | return h.WriteToPeer(b, h.getPeer(id)) 254 | } 255 | 256 | func (h *Homebrew) checkRepeaterID(id []byte) bool { 257 | // BrandMeister release 20190421-185653 switched from upper case to lower case hex digits 258 | return id != nil && bytes.EqualFold(id, h.id) 259 | } 260 | 261 | func (h *Homebrew) getPeer(id uint32) *Peer { 262 | h.mutex.Lock() 263 | defer h.mutex.Unlock() 264 | 265 | if peer, ok := h.PeerID[id]; ok { 266 | return peer 267 | } 268 | 269 | return nil 270 | } 271 | 272 | func (h *Homebrew) getPeerByAddr(addr *net.UDPAddr) *Peer { 273 | h.mutex.Lock() 274 | defer h.mutex.Unlock() 275 | 276 | if peer, ok := h.Peer[addr.String()]; ok { 277 | return peer 278 | } 279 | 280 | return nil 281 | } 282 | 283 | func (h *Homebrew) getPeers() []*Peer { 284 | h.mutex.Lock() 285 | defer h.mutex.Unlock() 286 | 287 | var peers = make([]*Peer, 0) 288 | for _, peer := range h.Peer { 289 | peers = append(peers, peer) 290 | } 291 | 292 | return peers 293 | } 294 | 295 | func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error { 296 | peer := h.getPeerByAddr(remote) 297 | if peer == nil { 298 | log.Debugf("ignored packet from unknown peer %s\n", remote) 299 | return nil 300 | } 301 | 302 | // Ignore packet that are clearly invalid, this is the minimum packet length for any Homebrew protocol frame 303 | if len(data) < 14 { 304 | return nil 305 | } 306 | 307 | if peer.Status != AuthDone { 308 | // Ignore DMR data at this stage 309 | if bytes.Equal(data[:4], DMRData) { 310 | return nil 311 | } 312 | 313 | if peer.Incoming { 314 | switch peer.Status { 315 | case AuthNone: 316 | switch { 317 | case bytes.Equal(data[:4], RepeaterLogin): 318 | if !peer.CheckRepeaterID(data[4:]) { 319 | log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[4:])) 320 | return h.WriteToPeer(append(MasterNAK, h.id...), peer) 321 | } 322 | 323 | // Peer is verified, generate a nonce 324 | nonce := make([]byte, 4) 325 | if _, err := rand.Read(nonce); err != nil { 326 | log.Errorf("peer %d@%s nonce generation failed: %v\n", peer.ID, remote, err) 327 | return h.WriteToPeer(append(MasterNAK, h.id...), peer) 328 | } 329 | 330 | peer.UpdateToken(nonce) 331 | peer.Status = AuthBegin 332 | return h.WriteToPeer(append(append(MasterACK, h.id...), nonce...), peer) 333 | 334 | default: 335 | // Ignore unauthenticated repeater, we're not going to reply unless it's 336 | // an actual login request; if it was indeed a valid repeater and we missed 337 | // anything, we rely on the remote end to retry to reconnect if it doesn't 338 | // get an answer in a timely manner. 339 | break 340 | } 341 | break 342 | 343 | case AuthBegin: 344 | switch { 345 | case bytes.Equal(data[:4], RepeaterKey): 346 | if !peer.CheckRepeaterID(data[4:]) { 347 | log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[4:])) 348 | return h.WriteToPeer(append(MasterNAK, h.id...), peer) 349 | } 350 | if len(data) != 76 { 351 | peer.Status = AuthNone 352 | return h.WriteToPeer(append(MasterNAK, h.id...), peer) 353 | } 354 | if !bytes.Equal(data[12:], peer.Token) { 355 | log.Errorf("peer %d@%s sent invalid key challenge token\n", peer.ID, remote) 356 | peer.Status = AuthNone 357 | return h.WriteToPeer(append(MasterNAK, h.id...), peer) 358 | } 359 | 360 | peer.Last.PingSent = time.Now() 361 | peer.Last.PongReceived = time.Now() 362 | peer.Status = AuthDone 363 | return h.WriteToPeer(append(MasterACK, h.id...), peer) 364 | } 365 | } 366 | } else { 367 | // Verify we have a matching peer ID 368 | if !h.checkRepeaterID(data[6:14]) { 369 | log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14])) 370 | return nil 371 | } 372 | 373 | switch peer.Status { 374 | case AuthNone: 375 | switch { 376 | case bytes.Equal(data[:6], MasterACK): 377 | log.Debugf("peer %d@%s sent nonce\n", peer.ID, remote) 378 | peer.Status = AuthBegin 379 | peer.UpdateToken(data[14:]) 380 | return h.handleAuth(peer) 381 | 382 | case bytes.Equal(data[:6], MasterNAK): 383 | log.Errorf("peer %d@%s refused login\n", peer.ID, remote) 384 | peer.Status = AuthFailed 385 | if peer.UnlinkOnAuthFailure { 386 | h.Unlink(peer.ID) 387 | } 388 | break 389 | 390 | default: 391 | log.Warningf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote) 392 | break 393 | } 394 | 395 | case AuthBegin: 396 | switch { 397 | case bytes.Equal(data[:6], MasterACK): 398 | log.Infof("peer %d@%s accepted login\n", peer.ID, remote) 399 | peer.Status = AuthDone 400 | peer.Last.PingSent = time.Now() 401 | peer.Last.PongReceived = time.Now() 402 | return h.WriteToPeer(h.Config.Bytes(), peer) 403 | 404 | case bytes.Equal(data[:6], MasterNAK): 405 | log.Errorf("peer %d@%s refused login\n", peer.ID, remote) 406 | peer.Status = AuthFailed 407 | if peer.UnlinkOnAuthFailure { 408 | h.Unlink(peer.ID) 409 | } 410 | break 411 | 412 | default: 413 | log.Warningf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote) 414 | break 415 | } 416 | } 417 | } 418 | } else { 419 | // Authentication is done 420 | if peer.Incoming { 421 | switch { 422 | case bytes.Equal(data[:4], DMRData): 423 | p, err := h.parseData(data) 424 | if err != nil { 425 | return err 426 | } 427 | return h.handlePacket(p, peer) 428 | 429 | case bytes.Equal(data[:6], MasterACK): 430 | break 431 | 432 | case len(data) == 15 && bytes.Equal(data[:7], MasterPing): 433 | return h.WriteToPeer(append(RepeaterPong, data[7:]...), peer) 434 | 435 | default: 436 | log.Warningf("peer %d@%s sent unexpected packet (status=%s):\n", peer.ID, remote, peer.Status.String()) 437 | log.Debug(hex.Dump(data)) 438 | break 439 | } 440 | } else { 441 | switch { 442 | case bytes.Equal(data[:4], DMRData): 443 | p, err := h.parseData(data) 444 | if err != nil { 445 | return err 446 | } 447 | return h.handlePacket(p, peer) 448 | 449 | case bytes.Equal(data[:6], MasterACK): 450 | if !h.checkRepeaterID(data[6:]) { 451 | log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14])) 452 | return nil 453 | } 454 | peer.Last.PingSent = time.Now() 455 | return h.WriteToPeer(append(MasterPing, h.id...), peer) 456 | 457 | case bytes.Equal(data[:6], MasterNAK): 458 | if !h.checkRepeaterID(data[6:]) { 459 | log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14])) 460 | return nil 461 | } 462 | 463 | log.Errorf("peer %d@%s deauthenticated us; re-authenticating\n", peer.ID, remote) 464 | peer.Status = AuthNone 465 | return h.handleAuth(peer) 466 | 467 | case len(data) == 15 && bytes.Equal(data[:7], RepeaterPong): 468 | if !h.checkRepeaterID(data[7:]) { 469 | log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14])) 470 | return nil 471 | } 472 | peer.Last.PongReceived = time.Now() 473 | break 474 | 475 | case len(data) == 10 && bytes.Equal(data[:6], MasterNAK): 476 | if !h.checkRepeaterID(data[6:]) { 477 | log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14])) 478 | return nil 479 | } 480 | log.Errorf("peer %d@%s sent NAK; re-establishing link\n", peer.ID, remote) 481 | peer.Status = AuthNone 482 | return h.handleAuth(peer) 483 | 484 | default: 485 | log.Warningf("peer %d@%s sent unexpected packet (status=%s):\n", peer.ID, remote, peer.Status.String()) 486 | log.Debug(hex.Dump(data)) 487 | break 488 | } 489 | } 490 | } 491 | 492 | return nil 493 | } 494 | 495 | func (h *Homebrew) handleAuth(peer *Peer) error { 496 | if !peer.Incoming { 497 | switch peer.Status { 498 | case AuthNone: 499 | // Send login packet 500 | return h.WriteToPeer(append(RepeaterLogin, h.id...), peer) 501 | 502 | case AuthBegin: 503 | // Send repeater key exchange packet 504 | return h.WriteToPeer(append(append(RepeaterKey, h.id...), peer.Token...), peer) 505 | } 506 | } 507 | return nil 508 | } 509 | 510 | func (h *Homebrew) handlePacket(p *dmr.Packet, peer *Peer) error { 511 | h.rxtx.Lock() 512 | defer h.rxtx.Unlock() 513 | 514 | // Record last received time 515 | h.last = time.Now() 516 | 517 | // Offload packet to handle callback 518 | if peer.PacketReceived != nil { 519 | return peer.PacketReceived(h, p) 520 | } 521 | if h.pf == nil { 522 | return errors.New("homebrew: no PacketReceived func defined to handle DMR packet") 523 | } 524 | 525 | return h.pf(h, p) 526 | } 527 | 528 | func (h *Homebrew) keepalive(stop <-chan bool) { 529 | for { 530 | select { 531 | case <-time.After(time.Second): 532 | now := time.Now() 533 | 534 | for _, peer := range h.getPeers() { 535 | // Ping protocol only applies to outgoing links, and also the auth retries 536 | // are entirely up to the peer. 537 | if peer.Incoming { 538 | switch peer.Status { 539 | case AuthDone: 540 | switch { 541 | case now.Sub(peer.Last.PingReceived) > PingTimeout: 542 | peer.Status = AuthNone 543 | log.Errorf("peer %d@%s not requesting to ping; dropping connection", peer.ID, peer.Addr) 544 | if err := h.WriteToPeer(append(MasterClosing, h.id...), peer); err != nil { 545 | log.Errorf("peer %d@%s close failed: %v\n", peer.ID, peer.Addr, err) 546 | } 547 | break 548 | } 549 | break 550 | } 551 | } else { 552 | switch peer.Status { 553 | case AuthNone, AuthBegin: 554 | switch { 555 | case now.Sub(peer.Last.PacketReceived) > AuthTimeout: 556 | peer.Status = AuthNone 557 | log.Errorf("peer %d@%s not responding to login; retrying\n", peer.ID, peer.Addr) 558 | if err := h.handleAuth(peer); err != nil { 559 | log.Errorf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err) 560 | } 561 | break 562 | } 563 | 564 | case AuthDone: 565 | switch { 566 | case now.Sub(peer.Last.PongReceived) > PingTimeout: 567 | peer.Status = AuthNone 568 | log.Errorf("peer %d@%s not responding to ping; trying to re-establish connection", peer.ID, peer.Addr) 569 | if err := h.WriteToPeer(append(RepeaterClosing, h.id...), peer); err != nil { 570 | log.Errorf("peer %d@%s close failed: %v\n", peer.ID, peer.Addr, err) 571 | } 572 | if err := h.handleAuth(peer); err != nil { 573 | log.Errorf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err) 574 | } 575 | break 576 | 577 | case now.Sub(peer.Last.PingSent) > PingInterval: 578 | peer.Last.PingSent = now 579 | if err := h.WriteToPeer(append(MasterPing, h.id...), peer); err != nil { 580 | log.Errorf("peer %d@%s ping failed: %v\n", peer.ID, peer.Addr, err) 581 | } 582 | break 583 | } 584 | } 585 | } 586 | } 587 | 588 | case <-stop: 589 | return 590 | } 591 | } 592 | } 593 | 594 | // parseData converts Homebrew packet format to DMR packet format 595 | func (h *Homebrew) parseData(data []byte) (*dmr.Packet, error) { 596 | p, err := ParseData(data) 597 | if err == nil { 598 | p.RepeaterID = h.Config.ID 599 | } 600 | return p, err 601 | } 602 | 603 | // parsePacket converts DMR packet format to Homebrew packet format suitable for sending on the wire 604 | func (h *Homebrew) parsePacket(p *dmr.Packet) []byte { 605 | var d = make([]byte, 53) 606 | 607 | // Signature, 4 bytes, "DMRD" 608 | copy(d[0:], DMRData) 609 | 610 | // Seq No, 1 byte 611 | d[4] = p.Sequence 612 | 613 | // Src ID, 3 bytes 614 | d[5] = uint8((p.SrcID >> 16) & 0xff) 615 | d[6] = uint8((p.SrcID >> 8) & 0xff) 616 | d[7] = uint8((p.SrcID & 0xff)) 617 | 618 | // Dst ID, 3 bytes 619 | d[8] = uint8((p.DstID >> 16) & 0xff) 620 | d[9] = uint8((p.DstID >> 8) & 0xff) 621 | d[10] = uint8((p.DstID & 0xff)) 622 | 623 | // RptrID, 4 bytes 624 | binary.LittleEndian.PutUint32(d[11:], p.RepeaterID) 625 | 626 | var s byte 627 | s |= (p.Timeslot & 0x01) // Slot no, 1 bit 628 | s |= (p.CallType & 0x01) << 1 // Call Type, 1 bit 629 | switch p.DataType { // Frame Type, 2 bits and Data Type or Voice Seq, 4 bits 630 | case dmr.VoiceBurstA: 631 | s |= 0x01 << 2 // Voice sync 632 | s |= (p.DataType - dmr.VoiceBurstA) << 4 633 | case dmr.VoiceBurstB, dmr.VoiceBurstC, dmr.VoiceBurstD, dmr.VoiceBurstE, dmr.VoiceBurstF: 634 | s |= 0x00 << 2 // Voice (no-op) 635 | s |= (p.DataType - dmr.VoiceBurstA) << 4 636 | default: 637 | s |= 0x02 << 2 // Data 638 | s |= p.DataType << 4 639 | } 640 | 641 | // StreamID, 4 bytes 642 | binary.BigEndian.PutUint32(d[16:], p.StreamID) 643 | 644 | // DMR Data, 33 bytes 645 | copy(d[20:], p.Data) 646 | return d 647 | } 648 | 649 | func (h *Homebrew) parseRepeaterID(data []byte) (uint32, error) { 650 | id, err := strconv.ParseUint(string(data), 16, 32) 651 | if err != nil { 652 | return 0, err 653 | } 654 | return uint32(id), nil 655 | } 656 | 657 | // Interface compliance check 658 | var _ dmr.Repeater = (*Homebrew)(nil) 659 | 660 | func packRepeaterID(id uint32) []byte { 661 | return []byte(fmt.Sprintf("%08X", id)) 662 | } 663 | 664 | // BuildData converts DMR packet format to Homebrew packet format. 665 | func BuildData(p *dmr.Packet, repeaterID uint32) []byte { 666 | var data = make([]byte, 53) 667 | copy(data[:4], DMRData) 668 | data[4] = p.Sequence 669 | data[5] = uint8(p.SrcID >> 16) 670 | data[6] = uint8(p.SrcID >> 8) 671 | data[7] = uint8(p.SrcID) 672 | data[8] = uint8(p.DstID >> 16) 673 | data[9] = uint8(p.DstID >> 8) 674 | data[10] = uint8(p.DstID) 675 | data[11] = uint8(repeaterID >> 24) 676 | data[12] = uint8(repeaterID >> 16) 677 | data[13] = uint8(repeaterID >> 8) 678 | data[14] = uint8(repeaterID) 679 | data[15] = p.Timeslot | (p.CallType << 1) 680 | data[16] = uint8(p.StreamID >> 24) 681 | data[17] = uint8(p.StreamID >> 16) 682 | data[18] = uint8(p.StreamID >> 8) 683 | data[19] = uint8(p.StreamID) 684 | copy(data[20:], p.Data) 685 | 686 | switch p.DataType { 687 | case dmr.VoiceBurstB, dmr.VoiceBurstC, dmr.VoiceBurstD, dmr.VoiceBurstE, dmr.VoiceBurstF: 688 | data[15] |= (0x00 << 2) 689 | data[15] |= (p.DataType - dmr.VoiceBurstA) << 4 690 | break 691 | case dmr.VoiceBurstA: 692 | data[15] |= (0x01 << 2) 693 | break 694 | default: 695 | data[15] |= (0x02 << 2) 696 | data[15] |= (p.DataType) << 4 697 | } 698 | 699 | return data 700 | } 701 | 702 | // ParseData converts Homebrew packet format to DMR packet format. 703 | func ParseData(data []byte) (*dmr.Packet, error) { 704 | if len(data) != 53 { 705 | return nil, fmt.Errorf("homebrew: expected 53 data bytes, got %d", len(data)) 706 | } 707 | 708 | var p = &dmr.Packet{ 709 | Sequence: data[4], 710 | SrcID: uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]), 711 | DstID: uint32(data[8])<<16 | uint32(data[9])<<8 | uint32(data[10]), 712 | RepeaterID: uint32(data[11])<<24 | uint32(data[12])<<16 | uint32(data[13])<<8 | uint32(data[14]), 713 | Timeslot: (data[15] >> 0) & 0x01, 714 | CallType: (data[15] >> 1) & 0x01, 715 | StreamID: uint32(data[16])<<24 | uint32(data[17])<<16 | uint32(data[18])<<8 | uint32(data[19]), 716 | } 717 | p.SetData(data[20:]) 718 | 719 | switch (data[15] >> 2) & 0x03 { 720 | case 0x00, 0x01: // voice (B-F), voice sync (A) 721 | p.DataType = dmr.VoiceBurstA + (data[15] >> 4) 722 | break 723 | case 0x02: // data sync 724 | p.DataType = (data[15] >> 4) 725 | break 726 | default: // unknown/unused 727 | return nil, errors.New("homebrew: unexpected frame type 0b11") 728 | } 729 | 730 | return p, nil 731 | } 732 | -------------------------------------------------------------------------------- /homebrew/peer.go: -------------------------------------------------------------------------------- 1 | package homebrew 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "net" 8 | "time" 9 | 10 | "github.com/pd0mz/go-dmr" 11 | ) 12 | 13 | // Peer is a remote repeater that also speaks the Homebrew protocol 14 | type Peer struct { 15 | ID uint32 16 | Addr *net.UDPAddr 17 | AuthKey []byte 18 | Status AuthStatus 19 | Nonce []byte 20 | Token []byte 21 | Incoming bool 22 | UnlinkOnAuthFailure bool 23 | PacketReceived dmr.PacketFunc 24 | Last struct { 25 | PacketSent time.Time 26 | PacketReceived time.Time 27 | PingSent time.Time 28 | PingReceived time.Time 29 | PongReceived time.Time 30 | } 31 | 32 | // Packed repeater ID 33 | id []byte 34 | } 35 | 36 | func (p *Peer) CheckRepeaterID(id []byte) bool { 37 | return id != nil && p.id != nil && bytes.Equal(id, p.id) 38 | } 39 | 40 | func (p *Peer) UpdateToken(nonce []byte) { 41 | p.Nonce = nonce 42 | hash := sha256.New() 43 | hash.Write(p.Nonce) 44 | hash.Write(p.AuthKey) 45 | p.Token = []byte(hex.EncodeToString(hash.Sum(nil))) 46 | } 47 | -------------------------------------------------------------------------------- /homebrew/repeaterconfiguration.go: -------------------------------------------------------------------------------- 1 | package homebrew 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pd0mz/go-dmr" 7 | ) 8 | 9 | // RepeaterConfiguration holds information about the current repeater. It 10 | // should be returned by a callback in the implementation, returning actual 11 | // information about the current repeater status. 12 | type RepeaterConfiguration struct { 13 | Callsign string 14 | ID uint32 // Our RepeaterID 15 | RXFreq uint32 16 | TXFreq uint32 17 | TXPower uint8 18 | ColorCode uint8 19 | Latitude float32 20 | Longitude float32 21 | Height uint16 22 | Location string 23 | Description string 24 | URL string 25 | SoftwareID string 26 | PackageID string 27 | } 28 | 29 | // Bytes returns the configuration as bytes. 30 | func (r *RepeaterConfiguration) Bytes() []byte { 31 | return []byte(r.String()) 32 | } 33 | 34 | // String returns the configuration as string. 35 | func (r *RepeaterConfiguration) String() string { 36 | if r.ColorCode < 1 { 37 | r.ColorCode = 1 38 | } 39 | if r.ColorCode > 15 { 40 | r.ColorCode = 15 41 | } 42 | if r.TXPower > 99 { 43 | r.TXPower = 99 44 | } 45 | if r.SoftwareID == "" { 46 | r.SoftwareID = dmr.SoftwareID 47 | } 48 | if r.PackageID == "" { 49 | r.PackageID = dmr.PackageID 50 | } 51 | 52 | var lat = fmt.Sprintf("%-08f", r.Latitude) 53 | if len(lat) > 8 { 54 | lat = lat[:8] 55 | } 56 | var lon = fmt.Sprintf("%-09f", r.Longitude) 57 | if len(lon) > 9 { 58 | lon = lon[:9] 59 | } 60 | 61 | var b = "RPTC" 62 | b += fmt.Sprintf("%-8s", r.Callsign) 63 | b += fmt.Sprintf("%08x", r.ID) 64 | b += fmt.Sprintf("%09d", r.RXFreq) 65 | b += fmt.Sprintf("%09d", r.TXFreq) 66 | b += fmt.Sprintf("%02d", r.TXPower) 67 | b += fmt.Sprintf("%02d", r.ColorCode) 68 | b += lat 69 | b += lon 70 | b += fmt.Sprintf("%03d", r.Height) 71 | b += fmt.Sprintf("%-20s", r.Location) 72 | b += fmt.Sprintf("%-20s", r.Description) 73 | b += fmt.Sprintf("%-124s", r.URL) 74 | b += fmt.Sprintf("%-40s", r.SoftwareID) 75 | b += fmt.Sprintf("%-40s", r.PackageID) 76 | return b 77 | } 78 | 79 | // ConfigFunc returns an actual RepeaterConfiguration instance when called. 80 | // This is used by the DMR repeater to poll for current configuration, 81 | // statistics and metrics. 82 | type ConfigFunc func() *RepeaterConfiguration 83 | -------------------------------------------------------------------------------- /ipsc/ipsc.go: -------------------------------------------------------------------------------- 1 | // Package ipsc implements the Motorola IP Site Connect protocol. 2 | package ipsc 3 | 4 | import ( 5 | "crypto/hmac" 6 | "crypto/sha1" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "fmt" 10 | "log" 11 | "net" 12 | "time" 13 | ) 14 | 15 | type Network struct { 16 | Disabled bool 17 | RadioID uint32 18 | AliveTimer time.Duration 19 | MaxMissed int 20 | IPSCMode string 21 | PeerOperDisabled bool 22 | TS1LinkDisabled bool 23 | TS2LinkDisabled bool 24 | CSBKCall bool 25 | RepeaterCallMonitoring bool `yaml:"rcm"` 26 | ConsoleApplicationDisabled bool 27 | XNLCall bool 28 | XNLMaster bool 29 | DataCall bool 30 | VoiceCall bool 31 | MasterPeer bool 32 | AuthKey string 33 | Master string 34 | MasterID uint32 35 | Listen string 36 | } 37 | 38 | type ipscPeerStatus struct { 39 | connected bool 40 | peerList bool 41 | keepAliveSent int 42 | keepAliveMissed int 43 | keepAliveOutstanding int 44 | keepAliveReceived int 45 | keepAliveRXTime time.Time 46 | } 47 | 48 | type ipscPeer struct { 49 | radioID uint32 50 | mode byte 51 | flags []byte 52 | status ipscPeerStatus 53 | } 54 | 55 | type IPSC struct { 56 | Network *Network 57 | Dump bool 58 | 59 | authKey []byte 60 | local struct { 61 | addr *net.UDPAddr 62 | radioID []byte 63 | mode byte 64 | flags []byte 65 | tsFlags []byte 66 | } 67 | peers map[uint32]ipscPeer 68 | master struct { 69 | addr *net.UDPAddr 70 | radioID uint32 71 | mode byte 72 | flags []byte 73 | status ipscPeerStatus 74 | } 75 | conn *net.UDPConn 76 | } 77 | 78 | func New(network *Network) (*IPSC, error) { 79 | c := &IPSC{ 80 | Network: network, 81 | } 82 | c.local.radioID = make([]byte, 4) 83 | c.local.flags = make([]byte, 4) 84 | c.peers = make(map[uint32]ipscPeer) 85 | c.master.flags = make([]byte, 4) 86 | 87 | binary.BigEndian.PutUint32(c.local.radioID, c.Network.RadioID) 88 | 89 | var err error 90 | 91 | if c.Network.AuthKey != "" { 92 | if c.authKey, err = hex.DecodeString(c.Network.AuthKey); err != nil { 93 | return nil, err 94 | } 95 | } 96 | if c.Network.AliveTimer == 0 { 97 | c.Network.AliveTimer = time.Second * 5 98 | } 99 | if !c.Network.PeerOperDisabled { 100 | c.local.mode |= FlagPeerOperational 101 | } 102 | switch c.Network.IPSCMode { 103 | case "analog": 104 | c.local.mode |= FlagPeerModeAnalog 105 | case "", "digital": 106 | c.local.mode |= FlagPeerModeDigital 107 | case "none": 108 | c.local.mode &= 0xff ^ MaskPeerMode 109 | default: 110 | return nil, fmt.Errorf("unknown IPSCMode %q", c.Network.IPSCMode) 111 | } 112 | if c.Network.TS1LinkDisabled { 113 | c.local.mode |= FlagIPSCTS1Off 114 | } else { 115 | c.local.mode |= FlagIPSCTS1On 116 | } 117 | if c.Network.TS2LinkDisabled { 118 | c.local.mode |= FlagIPSCTS2Off 119 | } else { 120 | c.local.mode |= FlagIPSCTS2On 121 | } 122 | 123 | if c.Network.CSBKCall { 124 | c.local.flags[2] |= FlagCSBKMessage 125 | } 126 | if c.Network.RepeaterCallMonitoring { 127 | c.local.flags[2] |= FlagRepeaterCallMonitoring 128 | } 129 | if !c.Network.ConsoleApplicationDisabled { 130 | c.local.flags[2] |= FlagConsoleApplication 131 | } 132 | 133 | if c.Network.XNLCall { 134 | c.local.flags[3] |= FlagXNLStatus 135 | if c.Network.XNLMaster { 136 | c.local.flags[3] |= FlagXNLMaster 137 | } else { 138 | c.local.flags[3] |= FlagXNLSlave 139 | } 140 | } 141 | if len(c.Network.AuthKey) > 0 { 142 | c.local.flags[3] |= FlagPacketAuthenticated 143 | } 144 | if c.Network.DataCall { 145 | c.local.flags[3] |= FlagDataCall 146 | } 147 | if c.Network.VoiceCall { 148 | c.local.flags[3] |= FlagVoiceCall 149 | } 150 | if c.Network.MasterPeer { 151 | c.local.flags[3] |= FlagMasterPeer 152 | } 153 | 154 | if c.Network.Listen == "" { 155 | c.Network.Listen = ":62030" 156 | } 157 | if c.local.addr, err = net.ResolveUDPAddr("udp", c.Network.Listen); err != nil { 158 | return nil, err 159 | } 160 | if c.Network.Master != "" { 161 | if c.master.addr, err = net.ResolveUDPAddr("udp", c.Network.Master); err != nil { 162 | return nil, err 163 | } 164 | } 165 | 166 | c.local.tsFlags = append([]byte{c.local.mode}, c.local.flags...) 167 | 168 | return c, nil 169 | } 170 | 171 | func (c *IPSC) Run() error { 172 | var err error 173 | if c.conn, err = net.ListenUDP("udp", c.local.addr); err != nil { 174 | return err 175 | } 176 | 177 | go c.peerMaintenance() 178 | 179 | for { 180 | var peer *net.UDPAddr 181 | var n int 182 | var b = make([]byte, 512) 183 | if n, peer, err = c.conn.ReadFromUDP(b); err != nil { 184 | log.Printf("error reading from %s: %v\n", peer, err) 185 | continue 186 | } 187 | 188 | if c.Dump { 189 | c.dump(peer, b[:n]) 190 | } 191 | if !c.authenticate(b[:n]) { 192 | log.Printf("authentication failed, dropping packet from %s\n", peer) 193 | continue 194 | } 195 | 196 | go c.parse(peer, c.payload(b[:n])) 197 | } 198 | 199 | return nil 200 | } 201 | 202 | func (c *IPSC) authenticate(data []byte) bool { 203 | if c.authKey == nil || len(c.authKey) == 0 { 204 | return true 205 | } 206 | 207 | payload := c.payload(data) 208 | hash := data[len(data)-10:] 209 | mac := hmac.New(sha1.New, c.authKey) 210 | mac.Write(payload) 211 | return hmac.Equal(hash, mac.Sum(nil)) 212 | } 213 | 214 | func (c *IPSC) parse(peer *net.UDPAddr, data []byte) { 215 | packetType := data[0] 216 | peerID := binary.BigEndian.Uint32(data[1:5]) 217 | //seq := data[5:6] 218 | 219 | switch { 220 | case MasterRequired[packetType]: 221 | if !c.validMaster(peerID) { 222 | log.Printf("%s: peer ID %d is not a valid master, expected %d\n", 223 | peer, peerID, c.Network.MasterID) 224 | return 225 | } 226 | 227 | switch packetType { 228 | case MasterAliveReply: 229 | c.resetKeepAlive(peerID) 230 | c.master.status.keepAliveReceived++ 231 | c.master.status.keepAliveRXTime = time.Now() 232 | } 233 | 234 | case packetType == MasterRegistrationReply: 235 | // We have successfully registered to a master 236 | c.master.radioID = peerID 237 | c.master.mode = data[5] 238 | c.master.flags = data[6:10] 239 | c.master.status.connected = true 240 | c.master.status.keepAliveOutstanding = 0 241 | log.Printf("registered to master %d\n", c.master.radioID) 242 | } 243 | } 244 | 245 | func (c *IPSC) payload(data []byte) []byte { 246 | if c.authKey == nil || len(c.authKey) == 0 { 247 | return data 248 | } 249 | return data[:len(data)-10] 250 | } 251 | 252 | func (c *IPSC) resetKeepAlive(peerID uint32) { 253 | if c.validMaster(peerID) { 254 | c.master.status.keepAliveOutstanding = 0 255 | return 256 | } 257 | if peer, ok := c.peers[peerID]; ok { 258 | peer.status.keepAliveOutstanding = 0 259 | } 260 | } 261 | 262 | func (c *IPSC) validMaster(peerID uint32) bool { 263 | return c.master.radioID == peerID 264 | } 265 | 266 | func (c *IPSC) dump(addr *net.UDPAddr, data []byte) { 267 | if len(data) < 7 { 268 | fmt.Printf("%d bytes of unreadable data from %s:\n", len(data), addr) 269 | fmt.Printf(hex.Dump(data)) 270 | return 271 | } 272 | 273 | fmt.Printf("%d bytes of data %s:\n", len(data), addr) 274 | fmt.Printf(hex.Dump(data)) 275 | 276 | packetType := data[0] 277 | peerID := binary.BigEndian.Uint32(data[1:5]) 278 | seq := data[5:6] 279 | 280 | switch packetType { 281 | case CallConfirmation: 282 | fmt.Println("call confirmation:") 283 | case TextMessageAck: 284 | fmt.Println("text message acknowledgement:") 285 | case CallMonStatus: 286 | fmt.Println("call monitor status:") 287 | case CallMonRepeat: 288 | fmt.Println("call monitor repeater:") 289 | case CallMonNACK: 290 | fmt.Println("call monitor nack:") 291 | case XCMPXNLControl: 292 | fmt.Println("XCMP/XNL control message:") 293 | case GroupVoice: 294 | fmt.Println("group voice:") 295 | case PVTVoice: 296 | fmt.Println("PVT voice:") 297 | case GroupData: 298 | fmt.Println("group data:") 299 | case PVTData: 300 | fmt.Println("PVT data:") 301 | case RPTWakeUp: 302 | fmt.Println("RPT wake up:") 303 | case UnknownCollision: 304 | fmt.Println("unknown collision:") 305 | case MasterRegistrationRequest: 306 | fmt.Println("master registration request:") 307 | case MasterRegistrationReply: 308 | fmt.Println("master registration reply:") 309 | case PeerListRequest: 310 | fmt.Println("peer list request:") 311 | case PeerListReply: 312 | fmt.Println("peer list reply:") 313 | case PeerRegistrationRequest: 314 | fmt.Println("peer registration request:") 315 | case PeerRegistrationReply: 316 | fmt.Println("peer registration reply:") 317 | case MasterAliveRequest: 318 | fmt.Println("master alive request:") 319 | case MasterAliveReply: 320 | fmt.Println("master alive reply:") 321 | case PeerAliveRequest: 322 | fmt.Println("peer alive request:") 323 | case PeerAliveReply: 324 | fmt.Println("peer alive reply:") 325 | case DeregistrationRequest: 326 | fmt.Println("de-registration request:") 327 | case DeregistrationReply: 328 | fmt.Println("de-registration reply:") 329 | default: 330 | fmt.Printf("unknown packet type 0x%02x:\n", packetType) 331 | } 332 | fmt.Printf("\tpeer id: %d\n", peerID) 333 | fmt.Printf("\tsequence: %v\n", seq) 334 | } 335 | 336 | func (c *IPSC) hashedPacket(key, data []byte) []byte { 337 | if key == nil || len(key) == 0 { 338 | return data 339 | } 340 | 341 | mac := hmac.New(sha1.New, key) 342 | mac.Write(data) 343 | 344 | hash := make([]byte, 20) 345 | hex.Encode(hash, mac.Sum(nil)) 346 | 347 | return append(data, hash...) 348 | } 349 | 350 | func (c *IPSC) peerMaintenance() { 351 | for { 352 | var p []byte 353 | if c.master.status.connected { 354 | log.Printf("sending keep-alive to master %d\n", c.master.radioID) 355 | r := []byte{MasterAliveRequest} 356 | r = append(r, c.local.radioID...) 357 | r = append(r, c.local.tsFlags...) 358 | r = append(r, []byte{LinkTypeIPSC, Version17, LinkTypeIPSC, Version16}...) 359 | p = c.hashedPacket(c.authKey, r) 360 | 361 | if c.master.status.keepAliveOutstanding > 0 { 362 | c.master.status.keepAliveMissed++ 363 | log.Printf("%d outstanding keep-alives from master\n", c.master.status.keepAliveOutstanding) 364 | } 365 | if c.master.status.keepAliveOutstanding > c.Network.MaxMissed { 366 | c.master.status.connected = false 367 | c.master.status.keepAliveOutstanding = 0 368 | log.Printf("connection to master %d lost\n", c.master.radioID) 369 | continue 370 | } 371 | 372 | c.master.status.keepAliveSent++ 373 | c.master.status.keepAliveOutstanding++ 374 | } else { 375 | log.Println("registering with master") 376 | r := []byte{MasterRegistrationRequest} 377 | r = append(r, c.local.radioID...) 378 | r = append(r, c.local.tsFlags...) 379 | r = append(r, []byte{LinkTypeIPSC, Version17, LinkTypeIPSC, Version16}...) 380 | p = c.hashedPacket(c.authKey, r) 381 | } 382 | 383 | if err := c.sendToMaster(p); err != nil { 384 | log.Fatalf("error sending registration request to master: %v\n", err) 385 | return 386 | } 387 | 388 | time.Sleep(c.Network.AliveTimer) 389 | } 390 | } 391 | 392 | func (c *IPSC) sendToMaster(data []byte) error { 393 | if c.Dump { 394 | c.dump(c.master.addr, data) 395 | } 396 | for len(data) > 0 { 397 | n, err := c.conn.WriteToUDP(data, c.master.addr) 398 | if err != nil { 399 | return err 400 | } 401 | data = data[n:] 402 | } 403 | return nil 404 | } 405 | -------------------------------------------------------------------------------- /ipsc/mask.go: -------------------------------------------------------------------------------- 1 | package ipsc 2 | 3 | type Mask uint8 4 | 5 | // IPSC mask values 6 | 7 | // Linking status 8 | /* 9 | Byte 1 - BIT FLAGS: 10 | 11 | xx.. .... = Peer Operational (01 only known valid value) 12 | ..xx .... = Peer MODE: 00 - No Radio, 01 - Analog, 10 - Digital 13 | .... xx.. = IPSC Slot 1: 10 on, 01 off 14 | .... ..xx = IPSC Slot 2: 10 on, 01 off 15 | */ 16 | const ( 17 | FlagPeerOperational = 0x40 18 | MaskPeerMode = 0x30 19 | FlagPeerModeAnalog = 0x10 20 | FlagPeerModeDigital = 0x20 21 | MaskIPSCTS1 = 0x0c 22 | MaskIPSCTS2 = 0x03 23 | FlagIPSCTS1On = 0x08 24 | FlagIPSCTS1Off = 0x04 25 | FlagIPSCTS2On = 0x02 26 | FlagIPSCTS2Off = 0x01 27 | ) 28 | 29 | // Service flags 30 | /* 31 | Byte 1 - 0x00 = Unknown 32 | Byte 2 - 0x00 = Unknown 33 | Byte 3 - BIT FLAGS: 34 | 35 | x... .... = CSBK Message 36 | .x.. .... = Repeater Call Monitoring 37 | ..x. .... = 3rd Party "Console" Application 38 | ...x xxxx = Unknown - default to 0 39 | */ 40 | const ( 41 | FlagCSBKMessage = 0x80 42 | FlagRepeaterCallMonitoring = 0x40 43 | FlagConsoleApplication = 0x20 44 | ) 45 | 46 | /* 47 | Byte 4 = BIT FLAGS: 48 | 49 | x... .... = XNL Connected (1=true) 50 | .x.. .... = XNL Master Device 51 | ..x. .... = XNL Slave Device 52 | ...x .... = Set if packets are authenticated 53 | .... x... = Set if data calls are supported 54 | .... .x.. = Set if voice calls are supported 55 | .... ..x. = Unknown - default to 0 56 | .... ...x = Set if master 57 | */ 58 | const ( 59 | FlagXNLStatus = 0x80 60 | FlagXNLMaster = 0x40 61 | FlagXNLSlave = 0x20 62 | FlagPacketAuthenticated = 0x10 63 | FlagDataCall = 0x08 64 | FlagVoiceCall = 0x04 65 | FlagMasterPeer = 0x01 66 | ) 67 | 68 | // Timeslot call and status byte 69 | /* 70 | Byte 17 of Group and Private Voice/Data Packets 71 | 72 | ..x.. ....TS Value (0=TS1, 1=TS2) 73 | .x... ....TS In Progress/End (0=In Progress, 1=End) 74 | 75 | Possible values: 0x00=TS1, 0x20=TS2, 0x40=TS1 End, 0x60=TS2 End 76 | */ 77 | 78 | // RTP mask values 79 | /* 80 | Bytes 1 and 2 of the RTP header are bit-fields, the rest are at least 81 | one byte long, and do not need masked. 82 | */ 83 | const ( 84 | // Byte 1 85 | RTPVersionMask Mask = 0xc0 86 | RTPPadMask Mask = 0x20 87 | RTPExtMask Mask = 0x10 88 | RTPCSICMask Mask = 0x0f 89 | // Byte 2 90 | RTPMRKRMask Mask = 0x80 91 | RTPPayTypeMask Mask = 0xf7 92 | ) 93 | -------------------------------------------------------------------------------- /ipsc/message.go: -------------------------------------------------------------------------------- 1 | package ipsc 2 | 3 | const ( 4 | // IPSC Version Information 5 | Version14 byte = 0x00 6 | Version15 byte = 0x00 7 | Version15A byte = 0x00 8 | Version16 byte = 0x01 9 | Version17 byte = 0x02 10 | Version18 byte = 0x02 11 | Version19 byte = 0x03 12 | Version22 byte = 0x04 13 | 14 | // Known IPSC Message Types 15 | CallConfirmation byte = 0x05 // Confirmation FROM the recipient of a confirmed call. 16 | TextMessageAck byte = 0x54 // Doesn't seem to mean success, though. This code is sent success or failure 17 | CallMonStatus byte = 0x61 // | 18 | CallMonRepeat byte = 0x62 // | Exact meaning unknown 19 | CallMonNACK byte = 0x63 // | 20 | XCMPXNLControl byte = 0x70 // XCMP/XNL control message 21 | GroupVoice byte = 0x80 22 | PVTVoice byte = 0x81 23 | GroupData byte = 0x83 24 | PVTData byte = 0x84 25 | RPTWakeUp byte = 0x85 // Similar to OTA DMR "wake up" 26 | UnknownCollision byte = 0x86 // Seen when two dmrlinks try to transmit at once 27 | MasterRegistrationRequest byte = 0x90 // FROM peer TO master 28 | MasterRegistrationReply byte = 0x91 // FROM master TO peer 29 | PeerListRequest byte = 0x92 // From peer TO master 30 | PeerListReply byte = 0x93 // From master TO peer 31 | PeerRegistrationRequest byte = 0x94 // Peer registration request 32 | PeerRegistrationReply byte = 0x95 // Peer registration reply 33 | MasterAliveRequest byte = 0x96 // FROM peer TO master 34 | MasterAliveReply byte = 0x97 // FROM master TO peer 35 | PeerAliveRequest byte = 0x98 // Peer keep alive request 36 | PeerAliveReply byte = 0x99 // Peer keep alive reply 37 | DeregistrationRequest byte = 0x9a // Request de-registration from system 38 | DeregistrationReply byte = 0x9b // De-registration reply 39 | 40 | // Link Type Values 41 | LinkTypeIPSC byte = 0x04 42 | ) 43 | 44 | var AnyPeerRequired = map[byte]bool{ 45 | GroupVoice: true, 46 | PVTVoice: true, 47 | GroupData: true, 48 | PVTData: true, 49 | CallMonStatus: true, 50 | CallMonRepeat: true, 51 | CallMonNACK: true, 52 | XCMPXNLControl: true, 53 | RPTWakeUp: true, 54 | DeregistrationRequest: true, 55 | } 56 | 57 | var PeerRequired = map[byte]bool{ 58 | PeerAliveRequest: true, 59 | PeerAliveReply: true, 60 | PeerRegistrationRequest: true, 61 | PeerRegistrationReply: true, 62 | } 63 | 64 | var MasterRequired = map[byte]bool{ 65 | PeerListReply: true, 66 | MasterAliveReply: true, 67 | } 68 | 69 | var UserGenerated = map[byte]bool{ 70 | GroupVoice: true, 71 | PVTVoice: true, 72 | GroupData: true, 73 | PVTData: true, 74 | } 75 | -------------------------------------------------------------------------------- /ipsc/packet.go: -------------------------------------------------------------------------------- 1 | package ipsc 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/pd0mz/go-dmr" 8 | ) 9 | 10 | const ( 11 | VoiceLCHeader uint16 = 0x1111 12 | TerminatorWithLC uint16 = 0x2222 13 | CSBK uint16 = 0x3333 14 | DataHeader uint16 = 0x4444 15 | Rate12Data uint16 = 0x5555 16 | Rate34Data uint16 = 0x6666 17 | VoiceDataA uint16 = 0xbbbb 18 | VoiceDataB uint16 = 0xcccc 19 | VoiceDataC uint16 = 0x7777 20 | VoiceDataD uint16 = 0x8888 21 | VoiceDataE uint16 = 0x9999 22 | VoiceDataF uint16 = 0xaaaa 23 | IPSCSync uint16 = 0xeeee 24 | UnknownSlotType uint16 = 0x0000 25 | ) 26 | 27 | const ( 28 | CallTypePrivate uint8 = iota 29 | CallTypeGroup 30 | ) 31 | 32 | var ( 33 | TimeslotName = map[uint8]string{ 34 | 0x00: "TS1", 35 | 0x01: "TS2", 36 | } 37 | FrameTypeName = map[uint8]string{ 38 | 0x00: "voice", 39 | 0x01: "voice sync", 40 | 0x02: "data sync", 41 | 0x03: "unused", 42 | } 43 | SlotTypeName = map[uint16]string{ 44 | 0x0000: "unknown", 45 | 0x1111: "voice LC header", 46 | 0x2222: "terminator with LC", 47 | 0x3333: "CSBK", 48 | 0x4444: "data header", 49 | 0x5555: "rate 1/2 data", 50 | 0x6666: "rate 3/4 data", 51 | 0x7777: "voice data C", 52 | 0x8888: "voice data D", 53 | 0x9999: "voice data E", 54 | 0xaaaa: "voice data F", 55 | 0xbbbb: "voice data A", 56 | 0xcccc: "voice data B", 57 | 0xeeee: "IPSC sync", 58 | } 59 | CallTypeName = map[uint8]string{ 60 | 0x00: "private", 61 | 0x01: "group", 62 | } 63 | ) 64 | 65 | type Packet struct { 66 | Timeslot uint8 // 0=ts1, 1=ts2 67 | FrameType uint8 68 | SlotType uint16 69 | CallType uint8 // 0=private, 1=group 70 | SrcID uint32 71 | DstID uint32 72 | Payload []byte // 34 bytes 73 | Bits []byte // 264 bits 74 | Sequence uint8 75 | } 76 | 77 | func (p *Packet) Dump() string { 78 | var s string 79 | s += fmt.Sprintf("timeslot..: 0b%02b (%s)\n", p.Timeslot, TimeslotName[p.Timeslot]) 80 | s += fmt.Sprintf("frame type: 0b%02b (%s)\n", p.FrameType, FrameTypeName[p.FrameType]) 81 | s += fmt.Sprintf("slot type.: 0x%04x (%s)\n", p.SlotType, SlotTypeName[p.SlotType]) 82 | s += fmt.Sprintf("call type.: 0x%02x (%s)\n", p.CallType, CallTypeName[p.CallType]) 83 | s += fmt.Sprintf("source....: %d\n", p.SrcID) 84 | s += fmt.Sprintf("target....: %d\n", p.DstID) 85 | s += fmt.Sprintf("payload...: %d bytes (swapped):\n", len(p.Payload)) 86 | s += hex.Dump(p.Payload) 87 | s += fmt.Sprintf("payload...: %d bits:\n", len(p.Bits)) 88 | s += hex.Dump(p.Bits) 89 | return s 90 | } 91 | 92 | func (p *Packet) InfoBits() []byte { 93 | var b = make([]byte, dmr.InfoBits) 94 | copy(b[0:dmr.InfoHalfBits], p.Bits[0:dmr.InfoHalfBits]) 95 | copy(b[dmr.InfoHalfBits:], p.Bits[dmr.InfoHalfBits+dmr.SlotTypeBits+dmr.SignalBits:]) 96 | return b 97 | } 98 | 99 | func (p *Packet) VoiceBits() []byte { 100 | var b = make([]byte, dmr.VoiceBits) 101 | copy(b[:dmr.VoiceHalfBits], p.Bits[:dmr.VoiceHalfBits]) 102 | copy(b[dmr.VoiceHalfBits:], p.Bits[dmr.VoiceHalfBits+dmr.SignalBits:]) 103 | return b 104 | } 105 | -------------------------------------------------------------------------------- /ipsc/payload.go: -------------------------------------------------------------------------------- 1 | package ipsc 2 | 3 | func SwapPayloadBytes(payload []byte) { 4 | for i := 0; i < len(payload)-1; i += 2 { 5 | payload[i], payload[i+1] = payload[i+1], payload[i] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lc/gpsinfo.go: -------------------------------------------------------------------------------- 1 | package lc 2 | 3 | import ( 4 | "fmt" 5 | 6 | dmr "github.com/pd0mz/go-dmr" 7 | ) 8 | 9 | // Data Format 10 | // ref: ETSI TS 102 361-2 7.2.18 11 | const ( 12 | ErrorLT2m uint8 = iota 13 | ErrorLT20m 14 | ErrorLT200m 15 | ErrorLT2km 16 | ErrorLT20km 17 | ErrorLE200km 18 | ErrorGT200km 19 | ErrorUnknown 20 | ) 21 | 22 | // PositionErrorName is a map of position error to string. 23 | var PositionErrorName = map[uint8]string{ 24 | ErrorLT2m: "< 2m", 25 | ErrorLT20m: "< 20m", 26 | ErrorLT200m: "< 200m", 27 | ErrorLT2km: "< 2km", 28 | ErrorLT20km: "< 20km", 29 | ErrorLE200km: "<= 200km", 30 | ErrorGT200km: "> 200km", 31 | ErrorUnknown: "unknown", 32 | } 33 | 34 | // GpsInfoPDU Conforms to ETSI TS 102 361-2 7.1.1.3 35 | type GpsInfoPDU struct { 36 | PositionError uint8 37 | Longitude uint32 38 | Latitude uint32 39 | } 40 | 41 | // ParseGpsInfoPDU parse gps info pdu 42 | func ParseGpsInfoPDU(data []byte) (*GpsInfoPDU, error) { 43 | if len(data) != 7 { 44 | return nil, fmt.Errorf("dmr/lc/talkeralias: expected 7 bytes, got %d", len(data)) 45 | } 46 | 47 | return &GpsInfoPDU{ 48 | PositionError: (data[0] & dmr.B00001110) >> 1, 49 | Longitude: uint32(data[0]&dmr.B00000001)<<24 | uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3]), 50 | Latitude: uint32(data[4])<<16 | uint32(data[5])<<8 | uint32(data[6]), 51 | }, nil 52 | } 53 | 54 | // Bytes returns GpsInfoPDU as bytes 55 | func (g *GpsInfoPDU) Bytes() []byte { 56 | return []byte{ 57 | uint8((g.PositionError&dmr.B00000111)<<1) | uint8((g.Longitude>>24)&dmr.B00000001), 58 | uint8(g.Longitude >> 16), 59 | uint8(g.Longitude >> 8), 60 | uint8(g.Longitude), 61 | uint8(g.Latitude >> 16), 62 | uint8(g.Latitude >> 8), 63 | uint8(g.Latitude), 64 | } 65 | } 66 | 67 | func (g *GpsInfoPDU) String() string { 68 | return fmt.Sprintf("GpsInfo: [ error: %s lon: %d lat: %d ]", 69 | PositionErrorName[g.PositionError], g.Longitude, g.Latitude) 70 | } 71 | -------------------------------------------------------------------------------- /lc/lc.go: -------------------------------------------------------------------------------- 1 | package lc 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/pd0mz/go-dmr" 8 | "github.com/pd0mz/go-dmr/fec" 9 | ) 10 | 11 | // Full Link Control Opcode 12 | const ( 13 | GroupVoiceChannelUser uint8 = 0x00 // B000000 14 | UnitToUnitVoiceChannelUser uint8 = 0x03 // B000011 15 | TalkerAliasHeader uint8 = 0x04 // B000100 16 | TalkerAliasBlk1 uint8 = 0x05 // B000101 17 | TalkerAliasBlk2 uint8 = 0x06 // B000110 18 | TalkerAliasBlk3 uint8 = 0x07 // B000111 19 | GpsInfo uint8 = 0x08 // B001000 20 | ) 21 | 22 | // LC is a Link Control message. 23 | type LC struct { 24 | CallType uint8 25 | Opcode uint8 26 | FeatureSetID uint8 27 | VoiceChannelUser *VoiceChannelUserPDU 28 | GpsInfo *GpsInfoPDU 29 | TalkerAliasHeader *TalkerAliasHeaderPDU 30 | TalkerAliasBlocks [3]*TalkerAliasBlockPDU 31 | } 32 | 33 | // Bytes packs the Link Control message to bytes. 34 | func (lc *LC) Bytes() []byte { 35 | var ( 36 | lcHeader = []byte{ 37 | lc.Opcode, 38 | lc.FeatureSetID, 39 | } 40 | innerPdu []byte 41 | ) 42 | 43 | switch lc.Opcode { 44 | case GroupVoiceChannelUser: 45 | fallthrough 46 | case UnitToUnitVoiceChannelUser: 47 | innerPdu = lc.VoiceChannelUser.Bytes() 48 | case TalkerAliasHeader: 49 | innerPdu = lc.TalkerAliasHeader.Bytes() 50 | case TalkerAliasBlk1: 51 | innerPdu = lc.TalkerAliasBlocks[0].Bytes() 52 | case TalkerAliasBlk2: 53 | innerPdu = lc.TalkerAliasBlocks[1].Bytes() 54 | case TalkerAliasBlk3: 55 | innerPdu = lc.TalkerAliasBlocks[2].Bytes() 56 | case GpsInfo: 57 | innerPdu = lc.GpsInfo.Bytes() 58 | } 59 | 60 | return append(lcHeader, innerPdu...) 61 | } 62 | 63 | func (lc *LC) String() string { 64 | var ( 65 | header = fmt.Sprintf("opcode %d, call type %s, feature set id %d", 66 | lc.Opcode, dmr.CallTypeName[lc.CallType], lc.FeatureSetID) 67 | r string 68 | ) 69 | 70 | switch lc.Opcode { 71 | case GroupVoiceChannelUser: 72 | fallthrough 73 | case UnitToUnitVoiceChannelUser: 74 | r = fmt.Sprintf("%s %v", header, lc.VoiceChannelUser) 75 | case GpsInfo: 76 | r = fmt.Sprintf("%s %v", header, lc.GpsInfo) 77 | case TalkerAliasHeader: 78 | r = fmt.Sprintf("%s %v", header, lc.TalkerAliasHeader) 79 | case TalkerAliasBlk1: 80 | r = fmt.Sprintf("%s %v", header, lc.TalkerAliasBlocks[0]) 81 | case TalkerAliasBlk2: 82 | r = fmt.Sprintf("%s %v", header, lc.TalkerAliasBlocks[1]) 83 | case TalkerAliasBlk3: 84 | r = fmt.Sprintf("%s %v", header, lc.TalkerAliasBlocks[2]) 85 | } 86 | 87 | return r 88 | } 89 | 90 | // ParseLC parses a packed Link Control message. 91 | func ParseLC(data []byte) (*LC, error) { 92 | if data == nil { 93 | return nil, errors.New("dmr/lc: data can't be nil") 94 | } 95 | if len(data) != 9 { 96 | return nil, fmt.Errorf("dmr/lc: expected 9 LC bytes, got %d", len(data)) 97 | } 98 | 99 | if data[0]&dmr.B10000000 > 0 { 100 | return nil, errors.New("dmr/lc: protect flag is not 0") 101 | } 102 | 103 | var ( 104 | err error 105 | fclo = data[0] & dmr.B00111111 106 | lc = &LC{ 107 | Opcode: fclo, 108 | FeatureSetID: data[1], 109 | } 110 | ) 111 | switch fclo { 112 | case GroupVoiceChannelUser: 113 | var pdu *VoiceChannelUserPDU 114 | lc.CallType = dmr.CallTypeGroup 115 | pdu, err = ParseVoiceChannelUserPDU(data[2:9]) 116 | lc.VoiceChannelUser = pdu 117 | case UnitToUnitVoiceChannelUser: 118 | var pdu *VoiceChannelUserPDU 119 | lc.CallType = dmr.CallTypePrivate 120 | pdu, err = ParseVoiceChannelUserPDU(data[2:9]) 121 | lc.VoiceChannelUser = pdu 122 | case TalkerAliasHeader: 123 | var pdu *TalkerAliasHeaderPDU 124 | pdu, err = ParseTalkerAliasHeaderPDU(data[2:9]) 125 | lc.TalkerAliasHeader = pdu 126 | case TalkerAliasBlk1: 127 | var pdu *TalkerAliasBlockPDU 128 | pdu, err = ParseTalkerAliasBlockPDU(data[2:9]) 129 | lc.TalkerAliasBlocks[0] = pdu 130 | case TalkerAliasBlk2: 131 | var pdu *TalkerAliasBlockPDU 132 | pdu, err = ParseTalkerAliasBlockPDU(data[2:9]) 133 | lc.TalkerAliasBlocks[1] = pdu 134 | case TalkerAliasBlk3: 135 | var pdu *TalkerAliasBlockPDU 136 | pdu, err = ParseTalkerAliasBlockPDU(data[2:9]) 137 | lc.TalkerAliasBlocks[2] = pdu 138 | case GpsInfo: 139 | var pdu *GpsInfoPDU 140 | pdu, err = ParseGpsInfoPDU(data[2:9]) 141 | lc.GpsInfo = pdu 142 | default: 143 | return nil, fmt.Errorf("dmr/lc: unknown FCLO %06b (%d)", fclo, fclo) 144 | } 145 | 146 | if err != nil { 147 | return nil, fmt.Errorf("error parsing link control header pdu: %s", err) 148 | } 149 | 150 | return lc, nil 151 | } 152 | 153 | // ParseFullLC parses a packed Link Control message and checks/corrects the Reed-Solomon check data. 154 | func ParseFullLC(data []byte) (*LC, error) { 155 | if data == nil { 156 | return nil, errors.New("dmr/full lc: data can't be nil") 157 | } 158 | if len(data) != 12 { 159 | return nil, fmt.Errorf("dmr/full lc: expected 12 bytes, got %d", len(data)) 160 | } 161 | 162 | syndrome := &fec.RS_12_9_Poly{} 163 | if err := fec.RS_12_9_CalcSyndrome(data, syndrome); err != nil { 164 | return nil, err 165 | } 166 | if !fec.RS_12_9_CheckSyndrome(syndrome) { 167 | if _, err := fec.RS_12_9_Correct(data, syndrome); err != nil { 168 | return nil, err 169 | } 170 | } 171 | 172 | return ParseLC(data[:9]) 173 | } 174 | -------------------------------------------------------------------------------- /lc/serviceoptions/serviceoptions.go: -------------------------------------------------------------------------------- 1 | package serviceoptions 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | dmr "github.com/pd0mz/go-dmr" 8 | ) 9 | 10 | // Priority Levels 11 | const ( 12 | NoPriority uint8 = iota 13 | Priority1 14 | Priority2 15 | Priority3 16 | ) 17 | 18 | // PriorityName is a map of priority level to string. 19 | var PriorityName = map[uint8]string{ 20 | NoPriority: "no priority", 21 | Priority1: "priority 1", 22 | Priority2: "priority 2", 23 | Priority3: "priority 3", 24 | } 25 | 26 | // ServiceOptions Conforms to ETSI TS 102-361-2 7.2.1 27 | type ServiceOptions struct { 28 | // Emergency service 29 | Emergency bool 30 | // Not defined in document 31 | Privacy bool 32 | // Broadcast service (only defined in group calls) 33 | Broadcast bool 34 | // Open Voice Call Mode 35 | OpenVoiceCallMode bool 36 | // Priority 3 (0b11) is the highest priority 37 | Priority uint8 38 | } 39 | 40 | // Byte packs the service options to a single byte. 41 | func (so *ServiceOptions) Byte() byte { 42 | var b byte 43 | if so.Emergency { 44 | b |= dmr.B00000001 45 | } 46 | if so.Privacy { 47 | b |= dmr.B00000010 48 | } 49 | if so.Broadcast { 50 | b |= dmr.B00010000 51 | } 52 | if so.OpenVoiceCallMode { 53 | b |= dmr.B00100000 54 | } 55 | b |= (so.Priority << 6) 56 | return b 57 | } 58 | 59 | // String representatation of the service options. 60 | func (so *ServiceOptions) String() string { 61 | var part = []string{} 62 | if so.Emergency { 63 | part = append(part, "emergency") 64 | } 65 | if so.Privacy { 66 | part = append(part, "privacy") 67 | } 68 | if so.Broadcast { 69 | part = append(part, "broadcast") 70 | } 71 | if so.OpenVoiceCallMode { 72 | part = append(part, "Open Voice Call Mode") 73 | } 74 | part = append(part, fmt.Sprintf("%s (%d)", PriorityName[so.Priority], so.Priority)) 75 | return strings.Join(part, ", ") 76 | } 77 | 78 | // ParseServiceOptions parses the service options byte. 79 | func ParseServiceOptions(data byte) ServiceOptions { 80 | return ServiceOptions{ 81 | Emergency: (data & dmr.B00000001) > 0, 82 | Privacy: (data & dmr.B00000010) > 0, 83 | Broadcast: (data & dmr.B00010000) > 0, 84 | OpenVoiceCallMode: (data & dmr.B00100000) > 0, 85 | Priority: (data & dmr.B11000000) >> 6, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lc/talkeralias.go: -------------------------------------------------------------------------------- 1 | package lc 2 | 3 | import ( 4 | "fmt" 5 | 6 | dmr "github.com/pd0mz/go-dmr" 7 | ) 8 | 9 | // Data Format 10 | // ref: ETSI TS 102 361-2 7.2.18 11 | const ( 12 | Format7Bit uint8 = iota 13 | FormatISO8Bit 14 | FormatUTF8 15 | FormatUTF16BE 16 | ) 17 | 18 | // DataFormatName is a map of data format to string. 19 | var DataFormatName = map[uint8]string{ 20 | Format7Bit: "7 bit", 21 | FormatISO8Bit: "ISO 8 bit", 22 | FormatUTF8: "unicode utf-8", 23 | FormatUTF16BE: "unicode utf-16be", 24 | } 25 | 26 | // TalkerAliasHeaderPDU Conforms to ETSI TS 102 361-2 7.1.1.4 27 | type TalkerAliasHeaderPDU struct { 28 | DataFormat uint8 29 | Length uint8 30 | Data []byte 31 | } 32 | 33 | // TalkerAliasBlockPDU Conforms to ETSI TS 102 361-2 7.1.1.5 34 | type TalkerAliasBlockPDU struct { 35 | Data []byte 36 | } 37 | 38 | func movebit(src []byte, srcByte int, srcBit int, dst []byte, dstByte int, dstBit int) { 39 | bit := (src[srcByte] >> uint8(srcBit)) & dmr.B00000001 40 | if bit >= 1 { 41 | dst[dstByte] |= 1 << uint8(dstBit) 42 | } else { 43 | dst[dstByte] &= 0 << uint8(dstBit) 44 | } 45 | } 46 | 47 | // ParseTalkerAliasHeaderPDU parses TalkerAliasHeader PDU from bytes 48 | func ParseTalkerAliasHeaderPDU(data []byte) (*TalkerAliasHeaderPDU, error) { 49 | if len(data) != 7 { 50 | return nil, fmt.Errorf("dmr/lc/talkeralias: expected 7 bytes, got %d", len(data)) 51 | } 52 | 53 | dataFormat := (data[0] & dmr.B11000000) >> 6 54 | 55 | var out []byte 56 | if dataFormat == Format7Bit { 57 | // it will reorganize the bits in the array and return []byte with 7bit chars 58 | // in each position 59 | out = make([]byte, 7) 60 | for i := 7; i < 56; i++ { 61 | movebit(data, i/8, (7 - (i % 8)), out, (i-7)/7, 6-(i%7)) 62 | } 63 | } else { 64 | out = data[1:6] 65 | } 66 | 67 | return &TalkerAliasHeaderPDU{ 68 | DataFormat: dataFormat, 69 | Length: (data[0] & dmr.B00111110) >> 1, 70 | Data: out, 71 | }, nil 72 | } 73 | 74 | // Bytes returns object as bytes 75 | func (t *TalkerAliasHeaderPDU) Bytes() []byte { 76 | var ( 77 | out []byte 78 | bit49 byte 79 | ) 80 | 81 | if t.DataFormat == Format7Bit { 82 | out = make([]byte, 6) 83 | for i := 7; i < 56; i++ { 84 | movebit(t.Data, (i-7)/7, 6-(i%7), out, (i/8)-1, (7 - (i % 8))) 85 | } 86 | } else { 87 | out = t.Data 88 | } 89 | 90 | return []byte{ 91 | ((t.DataFormat << 6) & dmr.B11000000) | ((t.Length << 1) & dmr.B00111110) | (bit49 & dmr.B00000001), 92 | out[0], 93 | out[1], 94 | out[2], 95 | out[3], 96 | out[4], 97 | out[5], 98 | } 99 | } 100 | 101 | // DataAsString Returns data part of PDU encoded as string 102 | func (t *TalkerAliasHeaderPDU) DataAsString() string { 103 | return string(t.Data) 104 | } 105 | 106 | func (t *TalkerAliasHeaderPDU) String() string { 107 | return fmt.Sprintf("TalkerAliasHeader: [ format: %s, length: %d, data: \"%s\" ]", 108 | DataFormatName[t.DataFormat], t.Length, t.DataAsString()) 109 | } 110 | 111 | // ParseTalkerAliasBlockPDU parse talker alias block pdu 112 | func ParseTalkerAliasBlockPDU(data []byte) (*TalkerAliasBlockPDU, error) { 113 | if len(data) != 7 { 114 | return nil, fmt.Errorf("dmr/lc/talkeralias: expected 7 bytes, got %d", len(data)) 115 | } 116 | 117 | return &TalkerAliasBlockPDU{ 118 | Data: data[0:6], 119 | }, nil 120 | } 121 | 122 | // Bytes returns object as bytes 123 | func (t *TalkerAliasBlockPDU) Bytes() []byte { 124 | return t.Data 125 | } 126 | 127 | // DataAsString Returns data part of PDU encoded as string 128 | func (t *TalkerAliasBlockPDU) DataAsString() string { 129 | return string(t.Data) 130 | } 131 | 132 | func (t *TalkerAliasBlockPDU) String() string { 133 | return fmt.Sprintf("TalkerAliasBlock: [ data: \"%s\" ]", t.DataAsString()) 134 | } 135 | -------------------------------------------------------------------------------- /lc/voicechanneluser.go: -------------------------------------------------------------------------------- 1 | package lc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pd0mz/go-dmr/lc/serviceoptions" 7 | ) 8 | 9 | // VoiceChannelUserPDU Conforms to ETSI TS 102-361-2 7.1.1.(1 and 2) 10 | type VoiceChannelUserPDU struct { 11 | ServiceOptions serviceoptions.ServiceOptions 12 | DstID uint32 13 | SrcID uint32 14 | } 15 | 16 | // ParseVoiceChannelUserPDU Parses either Group Voice Channel User or 17 | // Unit to Unit Channel User PDUs 18 | func ParseVoiceChannelUserPDU(data []byte) (*VoiceChannelUserPDU, error) { 19 | if len(data) != 7 { 20 | return nil, fmt.Errorf("dmr/lc/voicechanneluser: expected 7 bytes, got %d", len(data)) 21 | } 22 | 23 | return &VoiceChannelUserPDU{ 24 | ServiceOptions: serviceoptions.ParseServiceOptions(data[0]), 25 | DstID: uint32(data[1])<<16 | uint32(data[2])<<8 | uint32(data[3]), 26 | SrcID: uint32(data[4])<<16 | uint32(data[5])<<8 | uint32(data[6]), 27 | }, nil 28 | } 29 | 30 | // Bytes packs the Voice Channel User PDU message to bytes. 31 | func (v *VoiceChannelUserPDU) Bytes() []byte { 32 | return []byte{ 33 | v.ServiceOptions.Byte(), 34 | uint8(v.DstID >> 16), 35 | uint8(v.DstID >> 8), 36 | uint8(v.DstID), 37 | uint8(v.SrcID >> 16), 38 | uint8(v.SrcID >> 8), 39 | uint8(v.SrcID), 40 | } 41 | } 42 | 43 | func (v *VoiceChannelUserPDU) String() string { 44 | return fmt.Sprintf("VoiceChannelUser: [ %d->%d, service options %s ]", 45 | v.SrcID, v.DstID, v.ServiceOptions.String()) 46 | } 47 | -------------------------------------------------------------------------------- /packet.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | // Data Type information element definitions, DMR Air Interface (AI) protocol, Table 6.1 4 | const ( 5 | PrivacyIndicator uint8 = iota // Privacy Indicator information in a standalone burst 6 | VoiceLC // Indicates the beginning of voice transmission, carries addressing information 7 | TerminatorWithLC // Indicates the end of transmission, carries LC information 8 | CSBK // Carries a control block 9 | MultiBlockControl // Header for multi-block control 10 | MultiBlockControlContinuation // Follow-on blocks for multi-block control 11 | Data // Carries addressing and numbering of packet data blocks 12 | Rate12Data // Payload for rate 1/2 packet data 13 | Rate34Data // Payload for rate 3⁄4 packet data 14 | Idle // Fills channel when no info to transmit 15 | VoiceBurstA // Burst A marks the start of a superframe and always contains a voice SYNC pattern 16 | VoiceBurstB // Bursts B to F carry embedded signalling in place of the SYNC pattern 17 | VoiceBurstC // Bursts B to F carry embedded signalling in place of the SYNC pattern 18 | VoiceBurstD // Bursts B to F carry embedded signalling in place of the SYNC pattern 19 | VoiceBurstE // Bursts B to F carry embedded signalling in place of the SYNC pattern 20 | VoiceBurstF // Bursts B to F carry embedded signalling in place of the SYNC pattern 21 | IPSCSync 22 | UnknownSlotType 23 | ) 24 | 25 | var DataTypeName = map[uint8]string{ 26 | PrivacyIndicator: "privacy indicator", 27 | VoiceLC: "voice LC", 28 | TerminatorWithLC: "terminator with LC", 29 | CSBK: "control block", 30 | MultiBlockControl: "multi-block control", 31 | MultiBlockControlContinuation: "multi-block control follow-on", 32 | Data: "data", 33 | Rate12Data: "rate ½ packet data", 34 | Rate34Data: "rate ¾ packet data", 35 | Idle: "idle", 36 | VoiceBurstA: "voice (burst A)", 37 | VoiceBurstB: "voice (burst B)", 38 | VoiceBurstC: "voice (burst C)", 39 | VoiceBurstD: "voice (burst D)", 40 | VoiceBurstE: "voice (burst E)", 41 | VoiceBurstF: "voice (burst F)", 42 | IPSCSync: "IPSC sync", 43 | UnknownSlotType: "uknown", 44 | } 45 | 46 | // Call Type 47 | const ( 48 | CallTypePrivate uint8 = iota 49 | CallTypeGroup 50 | ) 51 | 52 | var CallTypeName = map[uint8]string{ 53 | CallTypePrivate: "private", 54 | CallTypeGroup: "group", 55 | } 56 | 57 | // Packet represents a frame transported by the Air Interface 58 | type Packet struct { 59 | // 0 for slot 1, 1 for slot 2 60 | Timeslot uint8 61 | 62 | // Starts at zero for each incoming transmission, wraps back to zero when 256 is reached 63 | Sequence uint8 64 | 65 | // Source and destination DMR ID 66 | SrcID uint32 67 | DstID uint32 68 | 69 | // 3 bytes registered DMR-ID for public repeaters, 4 bytes for private repeaters 70 | RepeaterID uint32 71 | 72 | // Random or incremented number which stays the same from PTT-press to PTT-release which identifies a stream 73 | StreamID uint32 74 | 75 | // Data Type or Slot type 76 | DataType uint8 77 | 78 | // 0 for group call, 1 for unit to unit 79 | CallType uint8 80 | 81 | // The on-air DMR data with possible FEC fixes to the AMBE data and/or Slot Type and/or EMB, etc 82 | Data []byte // 34 bytes 83 | Bits []byte // 264 bits 84 | } 85 | 86 | // EMBBits returns the frame EMB bits from the SYNC bits 87 | func (p *Packet) EMBBits() []byte { 88 | var ( 89 | b = make([]byte, EMBBits) 90 | o = EMBHalfBits + EMBSignallingLCFragmentBits 91 | sync = p.SyncBits() 92 | ) 93 | copy(b[:EMBHalfBits], sync[:EMBHalfBits]) 94 | copy(b[EMBHalfBits:], sync[o:o+EMBHalfBits]) 95 | return b 96 | } 97 | 98 | // InfoBits returns the frame Info bits 99 | func (p *Packet) InfoBits() []byte { 100 | var b = make([]byte, InfoBits) 101 | copy(b[0:InfoHalfBits], p.Bits[0:InfoHalfBits]) 102 | copy(b[InfoHalfBits:], p.Bits[InfoHalfBits+SlotTypeBits+SignalBits:]) 103 | return b 104 | } 105 | 106 | // SyncBits returns the frame SYNC bits 107 | func (p *Packet) SyncBits() []byte { 108 | return p.Bits[SyncOffsetBits : SyncOffsetBits+SyncBits] 109 | } 110 | 111 | // SlotType returns the frame Slot Type parsed from the Slot Type bits 112 | func (p *Packet) SlotType() []byte { 113 | return BitsToBytes(p.SlotTypeBits()) 114 | } 115 | 116 | // SlotTypeBits returns the SloT Type bits 117 | func (p *Packet) SlotTypeBits() []byte { 118 | var o = InfoHalfBits + SlotTypeHalfBits + SyncBits 119 | return append(p.Bits[InfoHalfBits:InfoHalfBits+SlotTypeHalfBits], p.Bits[o:o+SlotTypeHalfBits]...) 120 | } 121 | 122 | // VoiceBits returns the bits containing voice data 123 | func (p *Packet) VoiceBits() []byte { 124 | var b = make([]byte, VoiceBits) 125 | copy(b[:VoiceHalfBits], p.Bits[:VoiceHalfBits]) 126 | copy(b[VoiceHalfBits:], p.Bits[VoiceHalfBits+SignalBits:]) 127 | return b 128 | } 129 | 130 | func (p *Packet) SetData(data []byte) { 131 | p.Data = data 132 | p.Bits = BytesToBits(data) 133 | } 134 | 135 | // PacketFunc is a callback function that handles DMR packets 136 | type PacketFunc func(Repeater, *Packet) error 137 | -------------------------------------------------------------------------------- /repeater.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | // Repeater implements a repeater station. 4 | type Repeater interface { 5 | Active() bool 6 | Close() error 7 | ListenAndServe() error 8 | Send(*Packet) error 9 | 10 | GetPacketFunc() PacketFunc 11 | SetPacketFunc(PacketFunc) 12 | } 13 | -------------------------------------------------------------------------------- /sync.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | // Table 9.2: SYNC Patterns 8 | const ( 9 | SyncPatternBSSourcedVoice uint8 = iota 10 | SyncPatternBSSourcedData 11 | SyncPatternMSSourcedVoice 12 | SyncPatternMSSourcedData 13 | SyncPatternMSSourcedRC 14 | SyncPatternDirectVoiceTS1 15 | SyncPatternDirectDataTS1 16 | SyncPatternDirectVoiceTS2 17 | SyncPatternDirectDataTS2 18 | SyncPatternUnknown 19 | ) 20 | 21 | var ( 22 | bsSourcedVoice = []byte{0x75, 0x5f, 0xd7, 0xdf, 0x75, 0xf7} 23 | bsSourcedData = []byte{0xdf, 0xf5, 0x7d, 0x75, 0xdf, 0x5d} 24 | msSourcedVoice = []byte{0x7f, 0x7d, 0x5d, 0xd5, 0x7d, 0xfd} 25 | msSourcedData = []byte{0xd5, 0xd7, 0xf7, 0x7f, 0xd7, 0x57} 26 | msSourcedRC = []byte{0x77, 0xd5, 0x5f, 0x7d, 0xfd, 0x77} 27 | directVoiceTS1 = []byte{0x5d, 0x57, 0x7f, 0x77, 0x57, 0xff} 28 | directDataTS1 = []byte{0xf7, 0xfd, 0xd5, 0xdd, 0xfd, 0x55} 29 | directVoiceTS2 = []byte{0x7d, 0xff, 0xd5, 0xf5, 0x5d, 0x5f} 30 | directDataTS2 = []byte{0xd7, 0x55, 0x7f, 0x5f, 0xf7, 0xf5} 31 | SyncPatternName = map[uint8]string{ 32 | SyncPatternBSSourcedVoice: "bs sourced voice", 33 | SyncPatternBSSourcedData: "bs sourced data", 34 | SyncPatternMSSourcedVoice: "ms sourced voice", 35 | SyncPatternMSSourcedData: "ms sourced data", 36 | SyncPatternMSSourcedRC: "ms sourced rc", 37 | SyncPatternDirectVoiceTS1: "direct voice ts1", 38 | SyncPatternDirectDataTS1: "direct data ts1", 39 | SyncPatternDirectVoiceTS2: "direct voice ts2", 40 | SyncPatternDirectDataTS2: "direct data ts2", 41 | SyncPatternUnknown: "unknown", 42 | } 43 | ) 44 | 45 | func SyncPattern(bits []byte) uint8 { 46 | var b = BitsToBytes(bits) 47 | switch { 48 | case bytes.Equal(b, bsSourcedVoice): 49 | return SyncPatternBSSourcedVoice 50 | case bytes.Equal(b, bsSourcedData): 51 | return SyncPatternBSSourcedData 52 | case bytes.Equal(b, msSourcedVoice): 53 | return SyncPatternMSSourcedVoice 54 | case bytes.Equal(b, msSourcedData): 55 | return SyncPatternMSSourcedData 56 | case bytes.Equal(b, msSourcedRC): 57 | return SyncPatternMSSourcedRC 58 | case bytes.Equal(b, directVoiceTS1): 59 | return SyncPatternDirectVoiceTS1 60 | case bytes.Equal(b, directDataTS1): 61 | return SyncPatternDirectDataTS1 62 | case bytes.Equal(b, directVoiceTS2): 63 | return SyncPatternDirectVoiceTS2 64 | case bytes.Equal(b, directDataTS2): 65 | return SyncPatternDirectDataTS2 66 | default: 67 | return SyncPatternUnknown 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /terminal/terminal.go: -------------------------------------------------------------------------------- 1 | package terminal 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | "time" 9 | 10 | "github.com/op/go-logging" 11 | "github.com/pd0mz/go-dmr" 12 | "github.com/pd0mz/go-dmr/bptc" 13 | "github.com/pd0mz/go-dmr/lc" 14 | "github.com/pd0mz/go-dmr/trellis" 15 | "github.com/pd0mz/go-dmr/vbptc" 16 | ) 17 | 18 | var log = logging.MustGetLogger("dmr/terminal") 19 | 20 | const ( 21 | idle uint8 = iota 22 | dataCallActive 23 | voiceCallActive 24 | ) 25 | 26 | const VoiceFrameDuration = time.Millisecond * 60 27 | 28 | type Slot struct { 29 | call struct { 30 | start time.Time 31 | end time.Time 32 | } 33 | dstID, srcID uint32 34 | dataType uint8 35 | data struct { 36 | packetHeaderValid bool 37 | blocks []*dmr.DataBlock 38 | blocksExpected int 39 | blocksReceived int 40 | header *dmr.DataHeader 41 | } 42 | voice struct { 43 | lastFrame uint8 44 | streamID uint32 45 | } 46 | selectiveAckRequestsSent int 47 | rxSequence int 48 | fullMessageBlocks int 49 | embeddedSignalling *vbptc.VBPTC 50 | last struct { 51 | packetReceived time.Time 52 | } 53 | } 54 | 55 | func NewSlot() *Slot { 56 | s := &Slot{} 57 | 58 | // Expecting 8 rows of variable length BPTC coded embedded LC data. 59 | // It will contain 77 data bits (without the Hamming (16,11) checksums 60 | // and the last row of parity bits). 61 | s.embeddedSignalling = vbptc.New(8) 62 | 63 | return s 64 | } 65 | 66 | type VoiceFrameFunc func(*dmr.Packet, []byte) 67 | 68 | type Terminal struct { 69 | ID uint32 70 | Call string 71 | CallMap map[uint32]string 72 | Repeater dmr.Repeater 73 | TalkGroup []uint32 74 | SoftwareDelay bool 75 | 76 | accept map[uint32]bool 77 | slot []*Slot 78 | state uint8 79 | vff VoiceFrameFunc 80 | } 81 | 82 | func New(id uint32, call string, r dmr.Repeater) *Terminal { 83 | t := &Terminal{ 84 | ID: id, 85 | Call: call, 86 | Repeater: r, 87 | slot: []*Slot{NewSlot(), NewSlot(), NewSlot()}, 88 | accept: map[uint32]bool{id: true}, 89 | } 90 | 91 | r.SetPacketFunc(t.handlePacket) 92 | return t 93 | } 94 | 95 | func (t *Terminal) SetTalkGroups(tg []uint32) { 96 | t.accept = map[uint32]bool{t.ID: true} 97 | if tg != nil { 98 | for _, id := range tg { 99 | t.accept[id] = true 100 | } 101 | } 102 | } 103 | 104 | func (t *Terminal) SetVoiceFrameFunc(f VoiceFrameFunc) { 105 | t.vff = f 106 | } 107 | 108 | func (t *Terminal) Send(p *dmr.Packet) error { 109 | return t.Repeater.Send(p) 110 | } 111 | 112 | func (t *Terminal) fmt(p *dmr.Packet, f string) string { 113 | var fp = []string{ 114 | fmt.Sprintf("[slot %d][%02x][", 115 | p.Timeslot+1, 116 | p.Sequence), 117 | } 118 | if t.CallMap != nil { 119 | if call, ok := t.CallMap[p.SrcID]; ok { 120 | fp = append(fp, fmt.Sprintf("%-6s->", call)) 121 | } else { 122 | fp = append(fp, fmt.Sprintf("%-6d->", p.SrcID)) 123 | } 124 | if call, ok := t.CallMap[p.DstID]; ok { 125 | fp = append(fp, fmt.Sprintf("%-6s] ", call)) 126 | } else { 127 | fp = append(fp, fmt.Sprintf("%-6d] ", p.DstID)) 128 | } 129 | } else { 130 | fp = append(fp, fmt.Sprintf("%-6d->%-6d] ", p.SrcID, p.DstID)) 131 | } 132 | fp = append(fp, f) 133 | return strings.Join(fp, "") 134 | } 135 | 136 | func (t *Terminal) debugf(p *dmr.Packet, f string, v ...interface{}) { 137 | ff := t.fmt(p, f) 138 | if len(v) > 0 { 139 | log.Debugf(ff, v...) 140 | } else { 141 | log.Debug(ff) 142 | } 143 | } 144 | 145 | func (t *Terminal) infof(p *dmr.Packet, f string, v ...interface{}) { 146 | ff := t.fmt(p, f) 147 | if len(v) > 0 { 148 | log.Infof(ff, v...) 149 | } else { 150 | log.Info(ff) 151 | } 152 | } 153 | 154 | func (t *Terminal) warningf(p *dmr.Packet, f string, v ...interface{}) { 155 | ff := t.fmt(p, f) 156 | if len(v) > 0 { 157 | log.Warningf(ff, v...) 158 | } else { 159 | log.Warning(ff) 160 | } 161 | } 162 | 163 | func (t *Terminal) errorf(p *dmr.Packet, f string, v ...interface{}) { 164 | ff := t.fmt(p, f) 165 | if len(v) > 0 { 166 | log.Errorf(ff, v...) 167 | } else { 168 | log.Error(ff) 169 | } 170 | } 171 | 172 | func (t *Terminal) dataBlock(p *dmr.Packet, db *dmr.DataBlock) error { 173 | slot := t.slot[p.Timeslot] 174 | 175 | if slot.data.header == nil { 176 | return errors.New("terminal: logic error, header is nil?!") 177 | } 178 | if slot.data.header.ResponseRequested { 179 | // Only confirmed data blocks have serial numbers stored in them. 180 | if int(db.Serial) < len(slot.data.blocks) { 181 | slot.data.blocks[db.Serial] = db 182 | } else { 183 | t.warningf(p, "data block %d out of bounds (%d >= %d)", db.Serial, db.Serial, len(slot.data.blocks)) 184 | return nil 185 | } 186 | } else { 187 | slot.data.blocks[slot.data.blocksReceived] = db 188 | } 189 | 190 | slot.data.blocksReceived++ 191 | t.debugf(p, "data block %d/%d", slot.data.blocksReceived, slot.data.blocksExpected) 192 | if slot.data.blocksReceived == slot.data.blocksExpected { 193 | return t.dataBlockAssemble(p) 194 | } 195 | 196 | return nil 197 | } 198 | 199 | func (t *Terminal) dataBlockAssemble(p *dmr.Packet) error { 200 | slot := t.slot[p.Timeslot] 201 | 202 | var ( 203 | errorsFound bool 204 | selective = make([]bool, len(slot.data.blocks)) 205 | ) 206 | for i := 0; i < slot.fullMessageBlocks; i++ { 207 | if slot.data.blocks[i] == nil || !slot.data.blocks[i].OK { 208 | selective[i] = true 209 | errorsFound = true 210 | } 211 | } 212 | 213 | if errorsFound { 214 | _, responseOk := slot.data.header.Data.(*dmr.ResponseData) 215 | switch { 216 | case responseOk: 217 | t.debugf(p, "found erroneous blocks, not sending out ACK for response") 218 | return nil 219 | case slot.selectiveAckRequestsSent > 25: 220 | t.warningf(p, "found erroneous blocks, max selective ACK reached") 221 | return nil 222 | default: 223 | //t.sendSelectiveAck() 224 | return nil 225 | } 226 | } 227 | 228 | fragment, err := dmr.CombineDataBlocks(slot.data.blocks) 229 | if err != nil { 230 | return err 231 | } 232 | 233 | if fragment.Stored > 0 { 234 | // Response with data blocks? That must be a selective ACK 235 | if _, ok := slot.data.header.Data.(*dmr.ResponseData); ok { 236 | // FIXME(pd0mz): deal with this shit 237 | return nil 238 | } 239 | 240 | if err := t.dataBlockComplete(p, fragment); err != nil { 241 | return err 242 | } 243 | 244 | // If we are not waiting for an ack, then the data session ended 245 | if !slot.data.header.ResponseRequested { 246 | return t.dataCallEnd(p) 247 | } 248 | } 249 | return nil 250 | } 251 | 252 | func (t *Terminal) dataBlockComplete(p *dmr.Packet, f *dmr.DataFragment) error { 253 | slot := t.slot[p.Timeslot] 254 | 255 | var ( 256 | data []byte 257 | size int 258 | ddformat = dmr.DDFormatUTF16 259 | ) 260 | 261 | switch slot.data.header.ServiceAccessPoint { 262 | case dmr.ServiceAccessPointShortData: 263 | data = f.Data[2:] // Hytera has a 2 byte pre-padding 264 | size = f.Stored - 2 - 4 // Leave out the CRC 265 | 266 | if sdd, ok := slot.data.header.Data.(*dmr.ShortDataDefinedData); ok { 267 | ddformat = sdd.DDFormat 268 | } 269 | t.debugf(p, "bytes %d, format %s (%d)", size, dmr.DDFormatName[ddformat], ddformat) 270 | break 271 | 272 | default: 273 | t.warningf(p, "service accesspoint not implemented") 274 | } 275 | 276 | if data == nil || size == 0 { 277 | t.warningf(p, "no data in message") 278 | return nil 279 | 280 | } 281 | 282 | message, err := dmr.ParseMessageData(data[:size], ddformat, true) 283 | if err != nil { 284 | return err 285 | } 286 | 287 | t.infof(p, "message %q", message) 288 | return nil 289 | } 290 | 291 | func (t *Terminal) callEnd(p *dmr.Packet) error { 292 | switch t.state { 293 | case dataCallActive: 294 | return t.dataCallEnd(p) 295 | case voiceCallActive: 296 | return t.voiceCallEnd(p) 297 | } 298 | return nil 299 | } 300 | 301 | func (t *Terminal) dataCallEnd(p *dmr.Packet) error { 302 | slot := t.slot[p.Timeslot] 303 | 304 | if t.state != dataCallActive { 305 | return nil 306 | } 307 | 308 | slot.data.packetHeaderValid = false 309 | t.state = idle 310 | t.debugf(p, "data call ended") 311 | return nil 312 | } 313 | 314 | func (t *Terminal) dataCallStart(p *dmr.Packet) error { 315 | slot := t.slot[p.Timeslot] 316 | 317 | if slot.dstID != p.DstID || slot.srcID != p.SrcID || slot.dataType != p.DataType { 318 | if t.state == dataCallActive { 319 | if err := t.dataCallEnd(p); err != nil { 320 | return err 321 | } 322 | } 323 | } 324 | 325 | slot.data.packetHeaderValid = false 326 | slot.call.start = time.Now() 327 | slot.call.end = time.Time{} 328 | slot.dstID = p.DstID 329 | slot.srcID = p.SrcID 330 | t.state = dataCallActive 331 | t.debugf(p, "data call started") 332 | return nil 333 | } 334 | 335 | func (t *Terminal) voiceCallEnd(p *dmr.Packet) error { 336 | slot := t.slot[p.Timeslot] 337 | 338 | if t.state != voiceCallActive { 339 | return nil 340 | } 341 | 342 | slot.voice.streamID = 0 343 | t.state = idle 344 | t.debugf(p, "voice call ended") 345 | return nil 346 | } 347 | 348 | func (t *Terminal) voiceCallStart(p *dmr.Packet) error { 349 | slot := t.slot[p.Timeslot] 350 | 351 | if slot.dstID != p.DstID || slot.srcID != p.SrcID { 352 | if err := t.voiceCallEnd(p); err != nil { 353 | return err 354 | } 355 | } 356 | 357 | slot.voice.streamID = p.StreamID 358 | t.state = voiceCallActive 359 | 360 | t.debugf(p, "voice call started") 361 | return nil 362 | } 363 | 364 | func (t *Terminal) handlePacket(r dmr.Repeater, p *dmr.Packet) error { 365 | // Ignore packets not addressed to us or any of the talk groups we monitor 366 | if false && !t.accept[p.DstID] { 367 | //log.Debugf("[%d->%d] (%s, %#04b): ignored, not sent to me", p.SrcID, p.DstID, dmr.DataTypeName[p.DataType], p.DataType) 368 | return nil 369 | } 370 | 371 | var err error 372 | 373 | t.warningf(p, "handle packet: %s", dmr.DataTypeName[p.DataType]) 374 | //log.Debug(hex.Dump(p.Data)) 375 | 376 | // 377 | switch p.DataType { 378 | case dmr.CSBK: 379 | err = t.handleControlBlock(p) 380 | break 381 | case dmr.Data: 382 | err = t.handleData(p) 383 | break 384 | case dmr.Rate34Data: 385 | err = t.handleRate34Data(p) 386 | break 387 | case dmr.VoiceBurstA, dmr.VoiceBurstB, dmr.VoiceBurstC, dmr.VoiceBurstD, dmr.VoiceBurstE, dmr.VoiceBurstF: 388 | err = t.handleVoice(p) 389 | break 390 | case dmr.VoiceLC: 391 | err = t.handleVoiceLC(p) 392 | break 393 | case dmr.TerminatorWithLC: 394 | err = t.handleTerminatorWithLC(p) 395 | return nil 396 | default: 397 | t.warningf(p, "unhandled packet: %s", dmr.DataTypeName[p.DataType]) 398 | log.Debug(hex.Dump(p.Data)) 399 | return nil 400 | } 401 | 402 | if err != nil { 403 | t.errorf(p, "handle packet error: %v", err) 404 | } 405 | 406 | return err 407 | } 408 | 409 | func (t *Terminal) handleControlBlock(p *dmr.Packet) error { 410 | slot := t.slot[p.Timeslot] 411 | slot.last.packetReceived = time.Now() 412 | 413 | // This ends both data and voice calls 414 | if err := t.callEnd(p); err != nil { 415 | return err 416 | } 417 | 418 | var ( 419 | bits = p.InfoBits() 420 | data = make([]byte, 12) 421 | ) 422 | 423 | if err := bptc.Decode(bits, data); err != nil { 424 | return err 425 | } 426 | cb, err := dmr.ParseControlBlock(data) 427 | if err != nil { 428 | return err 429 | } 430 | 431 | t.debugf(p, cb.String()) 432 | 433 | return nil 434 | } 435 | 436 | func (t *Terminal) handleData(p *dmr.Packet) error { 437 | slot := t.slot[p.Timeslot] 438 | slot.last.packetReceived = time.Now() 439 | 440 | // This ends voice calls (if any) 441 | if err := t.voiceCallEnd(p); err != nil { 442 | return err 443 | } 444 | 445 | var ( 446 | bits = p.InfoBits() 447 | data = make([]byte, 12) 448 | ) 449 | 450 | if err := bptc.Decode(bits, data); err != nil { 451 | return err 452 | } 453 | 454 | h, err := dmr.ParseDataHeader(data, false) 455 | if err != nil { 456 | return err 457 | } 458 | 459 | slot.data.packetHeaderValid = false 460 | slot.data.blocksReceived = 0 461 | slot.selectiveAckRequestsSent = 0 462 | slot.rxSequence = 0 463 | 464 | t.debugf(p, h.String()) 465 | switch d := h.Data.(type) { 466 | case *dmr.ShortDataDefinedData: 467 | if d.FullMessage { 468 | slot.fullMessageBlocks = int(d.AppendedBlocks) 469 | slot.data.blocks = make([]*dmr.DataBlock, slot.fullMessageBlocks) 470 | t.debugf(p, "expecting %d data block", slot.fullMessageBlocks) 471 | } 472 | slot.data.blocksExpected = int(d.AppendedBlocks) 473 | err = t.dataCallStart(p) 474 | break 475 | 476 | default: 477 | t.warningf(p, "unhandled data header %T", h.Data) 478 | return nil 479 | } 480 | 481 | if err == nil { 482 | slot.data.header = h 483 | slot.data.packetHeaderValid = true 484 | } 485 | return err 486 | } 487 | 488 | func (t *Terminal) handleRate34Data(p *dmr.Packet) error { 489 | slot := t.slot[p.Timeslot] 490 | slot.last.packetReceived = time.Now() 491 | 492 | if t.state != dataCallActive { 493 | t.debugf(p, "no data call in process, ignoring rate ¾ data") 494 | return nil 495 | } 496 | if slot.data.header == nil { 497 | t.warningf(p, "got rate ¾ data, but no data header stored") 498 | return nil 499 | } 500 | 501 | var ( 502 | bits = p.InfoBits() 503 | data = make([]byte, 18) 504 | ) 505 | 506 | if err := trellis.Decode(bits, data); err != nil { 507 | return err 508 | } 509 | 510 | db, err := dmr.ParseDataBlock(data, dmr.Rate34Data, slot.data.header.ResponseRequested) 511 | if err != nil { 512 | return err 513 | } 514 | 515 | return t.dataBlock(p, db) 516 | } 517 | 518 | func (t *Terminal) handleTerminatorWithLC(p *dmr.Packet) error { 519 | // This ends both data and voice calls 520 | if err := t.callEnd(p); err != nil { 521 | return err 522 | } 523 | 524 | var ( 525 | bits = p.InfoBits() 526 | data = make([]byte, 12) 527 | ) 528 | if err := bptc.Decode(bits, data); err != nil { 529 | return err 530 | } 531 | 532 | // Applying CRC mask to the checksum. See DMR AI. spec. page 143. 533 | data[9] ^= 0x99 534 | data[10] ^= 0x99 535 | data[11] ^= 0x99 536 | 537 | lc, err := lc.ParseFullLC(data) 538 | if err != nil { 539 | return err 540 | } 541 | 542 | t.debugf(p, "terminator with lc: %s", lc.String()) 543 | 544 | return nil 545 | } 546 | 547 | func (t *Terminal) handleVoice(p *dmr.Packet) error { 548 | slot := t.slot[p.Timeslot] 549 | slot.last.packetReceived = time.Now() 550 | 551 | switch t.state { 552 | case voiceCallActive: 553 | if p.StreamID != slot.voice.streamID { 554 | // Only accept voice frames from the same stream 555 | t.debugf(p, "ignored frame, active stream id: %#08x, this stream id: %#08x", slot.voice.streamID, p.StreamID) 556 | return nil 557 | } 558 | default: 559 | t.voiceCallStart(p) 560 | break 561 | } 562 | 563 | // Check sync frame 564 | sync := p.SyncBits() 565 | patt := dmr.SyncPattern(sync) 566 | if patt != dmr.SyncPatternUnknown { 567 | t.debugf(p, "sync pattern %s", dmr.SyncPatternName[patt]) 568 | } else { 569 | // Not a sync frame, sync field should contain EMB 570 | bits, err := dmr.ParseEMBBitsFromSync(sync) 571 | if err != nil { 572 | return err 573 | } 574 | emb, err := dmr.ParseEMB(bits) 575 | if err != nil { 576 | return err 577 | } 578 | t.debugf(p, "embedded signalling %s", emb.String()) 579 | 580 | // Handling embedded signalling LC 581 | switch emb.LCSS { 582 | case dmr.SingleFragment: 583 | return nil // FIXME(pd0mz): unhandled 584 | case dmr.FirstFragment: 585 | slot.embeddedSignalling.Clear() 586 | break 587 | } 588 | 589 | if emb.LCSS == dmr.FirstFragment || emb.LCSS == dmr.Continuation || emb.LCSS == dmr.LastFragment { 590 | frag, err := dmr.ParseEmbeddedSignallingLCFromSyncBits(sync) 591 | if err != nil { 592 | return err 593 | } 594 | if err := slot.embeddedSignalling.AddBurst(frag); err != nil { 595 | return err 596 | } 597 | } 598 | 599 | if emb.LCSS == dmr.LastFragment { 600 | if err := slot.embeddedSignalling.CheckAndRepair(); err != nil { 601 | return err 602 | } 603 | 604 | var signalling = make([]byte, 77) 605 | if err := slot.embeddedSignalling.GetData(signalling); err != nil { 606 | return err 607 | } 608 | eslc, err := dmr.DeinterleaveEmbeddedSignallingLC(signalling) 609 | if err != nil { 610 | return err 611 | } 612 | if !eslc.Check() { 613 | return errors.New("embedded signalling LC checksum failed") 614 | } 615 | 616 | lc, err := lc.ParseLC(dmr.BitsToBytes(eslc.Bits)) 617 | if err != nil { 618 | return err 619 | } 620 | t.debugf(p, "voice embedded lc: %s", lc.String()) 621 | } 622 | } 623 | 624 | if t.vff != nil { 625 | t.vff(p, p.VoiceBits()) 626 | if t.SoftwareDelay { 627 | delta := time.Now().Sub(slot.last.packetReceived) 628 | if delta < VoiceFrameDuration { 629 | delay := VoiceFrameDuration - delta 630 | time.Sleep(delay) 631 | t.debugf(p, "software delay of %s", delay) 632 | } 633 | } 634 | } 635 | 636 | return nil 637 | } 638 | 639 | func (t *Terminal) handleVoiceLC(p *dmr.Packet) error { 640 | var ( 641 | bits = p.InfoBits() 642 | data = make([]byte, 12) 643 | ) 644 | if err := bptc.Decode(bits, data); err != nil { 645 | return err 646 | } 647 | 648 | // Applying CRC mask to the checksum. See DMR AI. spec. page 143. 649 | data[9] ^= 0x96 650 | data[10] ^= 0x96 651 | data[11] ^= 0x96 652 | 653 | lc, err := lc.ParseFullLC(data) 654 | if err != nil { 655 | return err 656 | } 657 | 658 | t.debugf(p, "voice header lc: %s", lc.String()) 659 | 660 | return nil 661 | } 662 | -------------------------------------------------------------------------------- /trellis/trellis.go: -------------------------------------------------------------------------------- 1 | package trellis 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/pd0mz/go-dmr" 8 | ) 9 | 10 | var ( 11 | // See DMR AI protocol spec. page 130. 12 | interleaveMatrix = []uint8{ 13 | 0, 1, 8, 9, 16, 17, 24, 25, 32, 33, 40, 41, 48, 49, 56, 57, 64, 65, 72, 73, 80, 81, 88, 89, 96, 97, 14 | 2, 3, 10, 11, 18, 19, 26, 27, 34, 35, 42, 43, 50, 51, 58, 59, 66, 67, 74, 75, 82, 83, 90, 91, 15 | 4, 5, 12, 13, 20, 21, 28, 29, 36, 37, 44, 45, 52, 53, 60, 61, 68, 69, 76, 77, 84, 85, 92, 93, 16 | 6, 7, 14, 15, 22, 23, 30, 31, 38, 39, 46, 47, 54, 55, 62, 63, 70, 71, 78, 79, 86, 87, 94, 95, 17 | } 18 | 19 | // See DMR AI protocol spec. page 129. 20 | encoderStateTransition = []uint8{ 21 | 0, 8, 4, 12, 2, 10, 6, 14, 22 | 4, 12, 2, 10, 6, 14, 0, 8, 23 | 1, 9, 5, 13, 3, 11, 7, 15, 24 | 5, 13, 3, 11, 7, 15, 1, 9, 25 | 3, 11, 7, 15, 1, 9, 5, 13, 26 | 7, 15, 1, 9, 5, 13, 3, 11, 27 | 2, 10, 6, 14, 0, 8, 4, 12, 28 | 6, 14, 0, 8, 4, 12, 2, 10, 29 | } 30 | ) 31 | 32 | // Decode is a convenience function that takes 196 Info bits and decodes them to 18 bytes (144 bits) binary using Trellis decoding. 33 | func Decode(bits []byte, bytes []byte) error { 34 | if bytes == nil { 35 | return errors.New("trellis: bytes can't be nil") 36 | } 37 | if len(bytes) < 18 { 38 | return fmt.Errorf("trellis: need buffer of at least 18 bytes, got %d", len(bytes)) 39 | } 40 | dibits, err := ExtractDibits(bits) 41 | if err != nil { 42 | return err 43 | } 44 | deinterleaved, err := Deinterleave(dibits) 45 | if err != nil { 46 | return err 47 | } 48 | points, err := ConstellationPoints(deinterleaved) 49 | if err != nil { 50 | return err 51 | } 52 | tribits, err := ExtractTribits(points) 53 | if err != nil { 54 | return err 55 | } 56 | binary, err := ExtractBinary(tribits) 57 | if err != nil { 58 | return err 59 | } 60 | copy(bytes, dmr.BitsToBytes(binary)) 61 | return nil 62 | } 63 | 64 | // ExtractDibits extracts dibits from bits. 65 | func ExtractDibits(bits []byte) ([]int8, error) { 66 | if len(bits) != dmr.InfoBits { 67 | return nil, fmt.Errorf("trellis: expected %d bits, got %d", dmr.InfoBits, len(bits)) 68 | } 69 | var dibits = make([]int8, 98) 70 | 71 | for i := 0; i < 196; i += 2 { 72 | o := i / 2 73 | switch { 74 | case bits[i] == 0 && bits[i+1] == 1: 75 | dibits[o] = 3 76 | break 77 | case bits[i] == 0 && bits[i+1] == 0: 78 | dibits[o] = 1 79 | break 80 | case bits[i] == 1 && bits[i+1] == 0: 81 | dibits[o] = -1 82 | break 83 | case bits[i] == 1 && bits[i+1] == 1: 84 | dibits[o] = -3 85 | break 86 | } 87 | } 88 | 89 | return dibits, nil 90 | } 91 | 92 | // Deinterleave the dibits according to DMR AI protocol spec. page 130. 93 | func Deinterleave(dibits []int8) ([]int8, error) { 94 | if dibits == nil { 95 | return nil, errors.New("trellis: dibits can't be nil") 96 | } 97 | if len(dibits) != 98 { 98 | return nil, fmt.Errorf("trellis: expected 98 dibits, got %d", len(dibits)) 99 | } 100 | 101 | var deinterleaved = make([]int8, 98) 102 | for i := 0; i < 98; i++ { 103 | deinterleaved[interleaveMatrix[i]] = dibits[i] 104 | } 105 | return deinterleaved, nil 106 | } 107 | 108 | // ConstellationPoints decodes the constellation points according to DMR AI protocol spec. page 129. 109 | func ConstellationPoints(dibits []int8) ([]uint8, error) { 110 | if dibits == nil { 111 | return nil, errors.New("trellis: dibits can't be nil") 112 | } 113 | if len(dibits) != 98 { 114 | return nil, fmt.Errorf("trellis: expected 98 dibits, got %d", len(dibits)) 115 | } 116 | 117 | var points = make([]uint8, 49) 118 | for i := 0; i < 98; i += 2 { 119 | o := i / 2 120 | switch { 121 | case dibits[i] == +1 && dibits[i+1] == -1: 122 | points[o] = 0 123 | break 124 | case dibits[i] == -1 && dibits[i+1] == -1: 125 | points[o] = 1 126 | break 127 | case dibits[i] == +3 && dibits[i+1] == -3: 128 | points[o] = 2 129 | break 130 | case dibits[i] == -3 && dibits[i+1] == -3: 131 | points[o] = 3 132 | break 133 | case dibits[i] == -3 && dibits[i+1] == -1: 134 | points[o] = 4 135 | break 136 | case dibits[i] == +3 && dibits[i+1] == -1: 137 | points[o] = 5 138 | break 139 | case dibits[i] == -1 && dibits[i+1] == -3: 140 | points[o] = 6 141 | break 142 | case dibits[i] == +1 && dibits[i+1] == -3: 143 | points[o] = 7 144 | break 145 | case dibits[i] == -3 && dibits[i+1] == +3: 146 | points[o] = 8 147 | break 148 | case dibits[i] == +3 && dibits[i+1] == +3: 149 | points[o] = 9 150 | break 151 | case dibits[i] == -1 && dibits[i+1] == +1: 152 | points[o] = 10 153 | break 154 | case dibits[i] == +1 && dibits[i+1] == +1: 155 | points[o] = 11 156 | break 157 | case dibits[i] == +1 && dibits[i+1] == +3: 158 | points[o] = 12 159 | break 160 | case dibits[i] == -1 && dibits[i+1] == +3: 161 | points[o] = 13 162 | break 163 | case dibits[i] == +3 && dibits[i+1] == +1: 164 | points[o] = 14 165 | break 166 | case dibits[i] == -3 && dibits[i+1] == +1: 167 | points[o] = 15 168 | break 169 | } 170 | } 171 | 172 | return points, nil 173 | } 174 | 175 | // ExtractTribits maps constellation points to Trellis tribits according to DMR AI protocol spec. page 129. 176 | func ExtractTribits(points []uint8) ([]uint8, error) { 177 | var ( 178 | match bool 179 | last uint8 180 | start int 181 | tribits = make([]uint8, 48) 182 | ) 183 | 184 | for i := 0; i < 48; i++ { 185 | start = int(last) * 8 186 | match = false 187 | for j := start; j < (start + 8); j++ { 188 | // Check if this constellation point matches an element of this row of the state table. 189 | if points[i] == encoderStateTransition[j] { 190 | match = true 191 | last = uint8(j - start) 192 | tribits[i] = uint8(last) 193 | } 194 | } 195 | 196 | if !match { 197 | return nil, fmt.Errorf("trellis: trellis tribit extract error at point %d, data is corrupted", i) 198 | } 199 | } 200 | 201 | return tribits, nil 202 | } 203 | 204 | // ExtractBinary maps the tribits back to bits. 205 | func ExtractBinary(tribits []uint8) ([]byte, error) { 206 | if tribits == nil { 207 | return nil, errors.New("trellis: tribits can't be nill") 208 | } 209 | if len(tribits) != 48 { 210 | return nil, fmt.Errorf("trellis: tribits length is %d, expected 48", len(tribits)) 211 | } 212 | 213 | var bits = make([]byte, 196) 214 | for i := 0; i < 144; i += 3 { 215 | o := i / 3 216 | 217 | if tribits[o]&dmr.B00000100 > 0 { 218 | bits[i] = 1 219 | } 220 | if tribits[o]&dmr.B00000010 > 0 { 221 | bits[i+1] = 1 222 | } 223 | if tribits[o]&dmr.B00000001 > 0 { 224 | bits[i+2] = 1 225 | } 226 | } 227 | 228 | return bits, nil 229 | } 230 | -------------------------------------------------------------------------------- /vbptc/vbptc.go: -------------------------------------------------------------------------------- 1 | // Package vbptc implements the Variable length BPTC for embedded signalling 2 | package vbptc 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | var ( 10 | // See page 136 of the DMR AI. spec. for the generator matrix. 11 | hamming_16_11_generator_matrix = []byte{ 12 | 1, 0, 0, 1, 1, 13 | 1, 1, 0, 1, 0, 14 | 1, 1, 1, 1, 1, 15 | 1, 1, 1, 0, 0, 16 | 0, 1, 1, 1, 0, 17 | 1, 0, 1, 0, 1, 18 | 0, 1, 0, 1, 1, 19 | 1, 0, 1, 1, 0, 20 | 1, 1, 0, 0, 1, 21 | 0, 1, 1, 0, 1, 22 | 0, 0, 1, 1, 1, 23 | // These are used to determine errors in the Hamming checksum bits. 24 | 1, 0, 0, 0, 0, 25 | 0, 1, 0, 0, 0, 26 | 0, 0, 1, 0, 0, 27 | 0, 0, 0, 1, 0, 28 | 0, 0, 0, 0, 1, 29 | } 30 | ) 31 | 32 | type VBPTC struct { 33 | matrix []byte 34 | row, col uint8 35 | expectedRows uint8 36 | } 37 | 38 | func New(expectedRows uint8) *VBPTC { 39 | return &VBPTC{ 40 | matrix: make([]byte, int(expectedRows)*16), 41 | expectedRows: expectedRows, 42 | } 43 | } 44 | 45 | func (v *VBPTC) freeSpace() int { 46 | var size = int(v.expectedRows) * 16 47 | var used = int(v.expectedRows)*int(v.col) + int(v.row) 48 | return size - used 49 | } 50 | 51 | // AddBurst adds the embedded signalling data to the matrix. 52 | func (v *VBPTC) AddBurst(bits []byte) error { 53 | if v.matrix == nil { 54 | return errors.New("vbptc: matrix can't be nil") 55 | } 56 | var free = v.freeSpace() 57 | if free == 0 { 58 | return errors.New("vbptc: no free space in matrix") 59 | } 60 | 61 | var adds = len(bits) 62 | if adds > free { 63 | adds = free 64 | } 65 | 66 | for i := 0; i < adds; i++ { 67 | v.matrix[v.col+v.row*16] = bits[i] 68 | v.row++ 69 | if v.row == v.expectedRows { 70 | v.col++ 71 | v.row = 0 72 | } 73 | } 74 | 75 | return nil 76 | } 77 | 78 | // CheckAndRepair checks data for errors and tries to repair them 79 | func (v *VBPTC) CheckAndRepair() error { 80 | if v.matrix == nil || v.expectedRows < 2 { 81 | return fmt.Errorf("vbptc: no data") 82 | } 83 | 84 | var ( 85 | row, col uint8 86 | errs = make([]byte, 5) 87 | ) 88 | 89 | // -1 because the last row contains only single parity check bits 90 | for row = 0; row < v.expectedRows-1; row++ { 91 | if !checkRow(v.matrix[row*16:], errs) { 92 | // If the Hamming(16, 11, 4) column check failed, see if we can find 93 | // the bit error location. 94 | pos, found := findPosition(errs) 95 | if !found { 96 | return fmt.Errorf("vbptc: hamming(16,11) check error, can't repair row #%d", row) 97 | } 98 | 99 | // Flip wrong bit 100 | v.matrix[row*16+pos] ^= 1 101 | if !checkRow(v.matrix[row*16:], errs) { 102 | return fmt.Errorf("vbptc: hamming(16,11) check error, couldn't repair row #%d", row) 103 | } 104 | } 105 | } 106 | 107 | for col = 0; col < 16; col++ { 108 | var parity uint8 109 | for row = 0; row < v.expectedRows-1; row++ { 110 | parity = (parity + v.matrix[row*16+col]) % 2 111 | } 112 | if parity != v.matrix[(v.expectedRows-1)*16+col] { 113 | return fmt.Errorf("vbptc: parity check error in column #%d", col) 114 | } 115 | } 116 | 117 | return nil 118 | } 119 | 120 | // Clear resets the variable BPTC matrix and cursor position 121 | func (v *VBPTC) Clear() { 122 | v.row = 0 123 | v.col = 0 124 | v.matrix = make([]byte, int(v.expectedRows)*16) 125 | } 126 | 127 | // GetData extracts data bits (discarding Hamming (16,11) and parity check bits) from the vbptc matrix. 128 | func (v *VBPTC) GetData(bits []byte) error { 129 | if v.matrix == nil || v.expectedRows == 0 { 130 | return errors.New("vbptc: no data in matrix") 131 | } 132 | if bits == nil { 133 | return errors.New("vbptc: bits can't be nil") 134 | } 135 | if len(bits) < 77 { 136 | return fmt.Errorf("vbptc: need at least 77 bits buffer, got %d", len(bits)) 137 | } 138 | 139 | var row, col uint8 140 | for row = 0; row < v.expectedRows-1; row++ { 141 | for col = 0; col < 11; col++ { 142 | bits[row*11+col] = v.matrix[row*16+col] 143 | } 144 | } 145 | 146 | return nil 147 | } 148 | 149 | func checkRow(bits, errs []byte) bool { 150 | if bits == nil || errs == nil { 151 | return false 152 | } 153 | 154 | getParity(bits, errs) 155 | errs[0] ^= bits[11] 156 | errs[1] ^= bits[12] 157 | errs[2] ^= bits[13] 158 | errs[3] ^= bits[14] 159 | errs[4] ^= bits[15] 160 | 161 | return errs[0] == 0 && errs[1] == 0 && errs[2] == 0 && errs[3] == 0 && errs[4] == 0 162 | } 163 | 164 | func findPosition(errs []byte) (uint8, bool) { 165 | for row := uint8(0); row < 16; row++ { 166 | var found = true 167 | switch { 168 | case hamming_16_11_generator_matrix[row*5] != errs[0]: 169 | found = false 170 | break 171 | case hamming_16_11_generator_matrix[row*5+1] != errs[1]: 172 | found = false 173 | break 174 | case hamming_16_11_generator_matrix[row*5+2] != errs[2]: 175 | found = false 176 | break 177 | case hamming_16_11_generator_matrix[row*5+3] != errs[3]: 178 | found = false 179 | break 180 | } 181 | if found { 182 | return row, true 183 | } 184 | } 185 | 186 | return 0, false 187 | } 188 | 189 | func getParity(bits, errs []byte) { 190 | errs[0] = (bits[0] ^ bits[1] ^ bits[2] ^ bits[3] ^ bits[5] ^ bits[7] ^ bits[8]) 191 | errs[1] = (bits[1] ^ bits[2] ^ bits[3] ^ bits[4] ^ bits[6] ^ bits[8] ^ bits[9]) 192 | errs[2] = (bits[2] ^ bits[3] ^ bits[4] ^ bits[5] ^ bits[7] ^ bits[9] ^ bits[10]) 193 | errs[3] = (bits[0] ^ bits[1] ^ bits[2] ^ bits[4] ^ bits[6] ^ bits[7] ^ bits[10]) 194 | errs[4] = (bits[0] ^ bits[2] ^ bits[5] ^ bits[6] ^ bits[8] ^ bits[9] ^ bits[10]) 195 | } 196 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | var ( 9 | Version = "0.2.1" // Version number 10 | SoftwareID = fmt.Sprintf("%s go-dmr %s", Version, runtime.GOOS) // Software identifier 11 | PackageID = fmt.Sprintf("%s/%s", SoftwareID, runtime.GOARCH) // Package identifier 12 | ) 13 | -------------------------------------------------------------------------------- /voice.go: -------------------------------------------------------------------------------- 1 | package dmr 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/pd0mz/go-dmr/crc/quadres_16_7" 8 | ) 9 | 10 | // EMB LCSS fragments. 11 | const ( 12 | SingleFragment uint8 = iota 13 | FirstFragment 14 | LastFragment 15 | Continuation 16 | ) 17 | 18 | // LCSSName is a map of LCSS fragment type to string. 19 | var LCSSName = map[uint8]string{ 20 | SingleFragment: "single fragment", 21 | FirstFragment: "first fragment", 22 | LastFragment: "last fragment", 23 | Continuation: "continuation", 24 | } 25 | 26 | // EMB contains embedded signalling. 27 | type EMB struct { 28 | ColorCode uint8 29 | LCSS uint8 30 | } 31 | 32 | func (emb *EMB) String() string { 33 | return fmt.Sprintf("color code %d, %s (%d)", emb.ColorCode, LCSSName[emb.LCSS], emb.LCSS) 34 | } 35 | 36 | // ParseEMB parses embedded signalling 37 | func ParseEMB(bits []byte) (*EMB, error) { 38 | if len(bits) != EMBBits { 39 | return nil, fmt.Errorf("dmr/emb: expected %d bits, got %d", EMBBits, len(bits)) 40 | } 41 | 42 | if !quadres_16_7.Check(bits) { 43 | return nil, errors.New("dmr/emb: checksum error") 44 | } 45 | 46 | if bits[4] != 0 { 47 | return nil, errors.New("dmr/emb: pi is not 0") 48 | } 49 | 50 | return &EMB{ 51 | ColorCode: uint8(bits[0])<<3 | uint8(bits[1])<<2 | uint8(bits[2])<<1 | uint8(bits[3]), 52 | LCSS: uint8(bits[5])<<1 | uint8(bits[6]), 53 | }, nil 54 | } 55 | 56 | // ParseEMBBitsFromSync extracts the embedded signalling bits from the SYNC bits. 57 | func ParseEMBBitsFromSync(sync []byte) ([]byte, error) { 58 | if sync == nil { 59 | return nil, errors.New("dmr/emb from sync: bits can't be nil") 60 | } 61 | if len(sync) != 48 { 62 | return nil, fmt.Errorf("dmr/emb from sync: expected 48 sync bits, got %d", len(sync)) 63 | } 64 | 65 | var bits = make([]byte, 16) 66 | copy(bits[:8], sync[:8]) 67 | copy(bits[8:], sync[8+32:]) 68 | return bits, nil 69 | } 70 | 71 | // ParseEmbeddedSignallingLCFromSyncBits extracts the embedded signalling LC from the SYNC bits. 72 | func ParseEmbeddedSignallingLCFromSyncBits(sync []byte) ([]byte, error) { 73 | if sync == nil { 74 | return nil, errors.New("dmr/emb lc from sync: bits can't be nil") 75 | } 76 | if len(sync) != 48 { 77 | return nil, fmt.Errorf("dmr/emb lc from sync: expected 48 sync bits, got %d", len(sync)) 78 | } 79 | 80 | var bits = make([]byte, 32) 81 | copy(bits, sync[8:40]) 82 | return bits, nil 83 | } 84 | 85 | // EmbeddedSignallingLC contains the embedded signalling LC and checksum. 86 | type EmbeddedSignallingLC struct { 87 | Bits []byte 88 | Checksum []byte 89 | } 90 | 91 | // Check verifies the checksum in the embedded signalling LC. 92 | func (eslc *EmbeddedSignallingLC) Check() bool { 93 | var checksum uint8 94 | checksum |= eslc.Checksum[0] << 4 95 | checksum |= eslc.Checksum[1] << 3 96 | checksum |= eslc.Checksum[2] << 2 97 | checksum |= eslc.Checksum[3] << 1 98 | checksum |= eslc.Checksum[4] << 0 99 | 100 | var data = BitsToBytes(eslc.Bits) 101 | var verify uint16 102 | for _, b := range data { 103 | verify += uint16(b) 104 | } 105 | 106 | var calculated = uint8(verify % 31) 107 | return calculated == checksum 108 | } 109 | 110 | // Interleave packs the embedded signalling LC to interleaved bits. 111 | func (eslc *EmbeddedSignallingLC) Interleave() []byte { 112 | var bits = make([]byte, 77) 113 | var j int 114 | for i := range bits { 115 | switch i { 116 | case 32: 117 | bits[i] = eslc.Checksum[0] 118 | break 119 | case 43: 120 | bits[i] = eslc.Checksum[1] 121 | break 122 | case 54: 123 | bits[i] = eslc.Checksum[2] 124 | break 125 | case 65: 126 | bits[i] = eslc.Checksum[3] 127 | break 128 | case 76: 129 | bits[i] = eslc.Checksum[4] 130 | break 131 | default: 132 | bits[i] = eslc.Bits[j] 133 | j++ 134 | } 135 | } 136 | 137 | return bits 138 | } 139 | 140 | // DeinterleaveEmbeddedSignallingLC deinterleaves the embedded signalling LC bits. 141 | func DeinterleaveEmbeddedSignallingLC(bits []byte) (*EmbeddedSignallingLC, error) { 142 | if bits == nil { 143 | return nil, errors.New("dmr/emb lc deinterleave: bits can't be nil") 144 | } 145 | if len(bits) != 77 { 146 | return nil, fmt.Errorf("dmr/emb lc deinterleave: expected 77 bits, got %d", len(bits)) 147 | } 148 | 149 | var eslc = &EmbeddedSignallingLC{ 150 | Bits: make([]byte, 72), 151 | Checksum: make([]byte, 5), 152 | } 153 | var j int 154 | for i, b := range bits { 155 | switch i { 156 | case 32: 157 | eslc.Checksum[0] = b 158 | break 159 | case 43: 160 | eslc.Checksum[1] = b 161 | break 162 | case 54: 163 | eslc.Checksum[2] = b 164 | break 165 | case 65: 166 | eslc.Checksum[3] = b 167 | break 168 | case 76: 169 | eslc.Checksum[4] = b 170 | break 171 | default: 172 | eslc.Bits[j] = b 173 | j++ 174 | } 175 | } 176 | 177 | return eslc, nil 178 | } 179 | --------------------------------------------------------------------------------