Checkpoint
parent
cca6817956
commit
4f2b16dfcb
@ -0,0 +1,144 @@
|
|||||||
|
package dmr
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func testCSBK(want *ControlBlock, t *testing.T) *ControlBlock {
|
||||||
|
want.SrcID = 2042214
|
||||||
|
want.DstID = 2043044
|
||||||
|
|
||||||
|
data, err := want.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("encode failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
test, err := ParseControlBlock(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decode failed: %v", err)
|
||||||
|
}
|
||||||
|
if test.SrcID != want.SrcID || test.DstID != want.DstID {
|
||||||
|
t.Fatal("decode failed, ID wrong")
|
||||||
|
}
|
||||||
|
|
||||||
|
return test
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSBKOutboundActivation(t *testing.T) {
|
||||||
|
want := &ControlBlock{
|
||||||
|
Opcode: OutboundActivationOpcode,
|
||||||
|
Data: &OutboundActivation{},
|
||||||
|
}
|
||||||
|
test := testCSBK(want, t)
|
||||||
|
|
||||||
|
_, ok := test.Data.(*OutboundActivation)
|
||||||
|
switch {
|
||||||
|
case !ok:
|
||||||
|
t.Fatalf("decode failed: expected UnitToUnitVoiceServiceRequest, got %T", test.Data)
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Logf("decode: %s", test.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSBKUnitToUnitVoiceServiceRequest(t *testing.T) {
|
||||||
|
want := &ControlBlock{
|
||||||
|
Opcode: UnitToUnitVoiceServiceRequestOpcode,
|
||||||
|
Data: &UnitToUnitVoiceServiceRequest{
|
||||||
|
Options: 0x2a,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
test := testCSBK(want, t)
|
||||||
|
|
||||||
|
d, ok := test.Data.(*UnitToUnitVoiceServiceRequest)
|
||||||
|
switch {
|
||||||
|
case !ok:
|
||||||
|
t.Fatalf("decode failed: expected UnitToUnitVoiceServiceRequest, got %T", test.Data)
|
||||||
|
|
||||||
|
case d.Options != 0x2a:
|
||||||
|
t.Fatalf("decode failed, options wrong")
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Logf("decode: %s", test.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSBKUnitToUnitVoiceServiceAnswerResponse(t *testing.T) {
|
||||||
|
want := &ControlBlock{
|
||||||
|
Opcode: UnitToUnitVoiceServiceAnswerResponseOpcode,
|
||||||
|
Data: &UnitToUnitVoiceServiceAnswerResponse{
|
||||||
|
Options: 0x17,
|
||||||
|
Response: 0x2a,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
test := testCSBK(want, t)
|
||||||
|
|
||||||
|
d, ok := test.Data.(*UnitToUnitVoiceServiceAnswerResponse)
|
||||||
|
switch {
|
||||||
|
case !ok:
|
||||||
|
t.Fatalf("decode failed: expected UnitToUnitVoiceServiceAnswerResponse, got %T", test.Data)
|
||||||
|
|
||||||
|
case d.Response != 0x2a:
|
||||||
|
t.Fatalf("decode failed, response wrong")
|
||||||
|
|
||||||
|
case d.Options != 0x17:
|
||||||
|
t.Fatalf("decode failed, options wrong")
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Logf("decode: %s", test.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSBKNegativeAcknowledgeResponse(t *testing.T) {
|
||||||
|
want := &ControlBlock{
|
||||||
|
Opcode: NegativeAcknowledgeResponseOpcode,
|
||||||
|
Data: &NegativeAcknowledgeResponse{
|
||||||
|
ServiceType: 0x01,
|
||||||
|
Reason: 0x02,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
test := testCSBK(want, t)
|
||||||
|
|
||||||
|
d, ok := test.Data.(*NegativeAcknowledgeResponse)
|
||||||
|
switch {
|
||||||
|
case !ok:
|
||||||
|
t.Fatalf("decode failed: expected NegativeAcknowledgeResponse, got %T", test.Data)
|
||||||
|
|
||||||
|
case d.ServiceType != 0x01:
|
||||||
|
t.Fatalf("decode failed, service type wrong")
|
||||||
|
|
||||||
|
case d.Reason != 0x02:
|
||||||
|
t.Fatalf("decode failed, reason wrong")
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Logf("decode: %s", test.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCSBKPreamble(t *testing.T) {
|
||||||
|
want := &ControlBlock{
|
||||||
|
Opcode: PreambleOpcode,
|
||||||
|
Data: &Preamble{
|
||||||
|
DataFollows: true,
|
||||||
|
DstIsGroup: false,
|
||||||
|
Blocks: 0x10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
test := testCSBK(want, t)
|
||||||
|
|
||||||
|
d, ok := test.Data.(*Preamble)
|
||||||
|
switch {
|
||||||
|
case !ok:
|
||||||
|
t.Fatalf("decode failed: expected Preamble, got %T", test.Data)
|
||||||
|
|
||||||
|
case !d.DataFollows:
|
||||||
|
t.Fatalf("decode failed, data follows wrong")
|
||||||
|
|
||||||
|
case d.DstIsGroup:
|
||||||
|
t.Fatalf("decode failed, dst is group wrong")
|
||||||
|
|
||||||
|
case d.Blocks != 0x10:
|
||||||
|
t.Fatalf("decode failed, blocks wrong")
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Logf("decode: %s", test.String())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
package dmr
|
||||||
|
|
||||||
|
const (
|
||||||
|
// G(x) = x^9+x^6+x^4+x^3+1
|
||||||
|
crc9poly = 0x59
|
||||||
|
// G(x) = x^16+x^12+x^5+1
|
||||||
|
crc16poly = 0x1021
|
||||||
|
crc32poly = 0x04c11db7
|
||||||
|
)
|
||||||
|
|
||||||
|
func crc9(crc *uint16, b uint8, bits int) {
|
||||||
|
var v uint8 = 0x80
|
||||||
|
for i := 0; i < 8-bits; i++ {
|
||||||
|
v >>= 1
|
||||||
|
}
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
xor := (*crc)&0x0100 > 0
|
||||||
|
(*crc) <<= 1
|
||||||
|
// Limit the number of shift registers to 9.
|
||||||
|
*crc &= 0x01ff
|
||||||
|
if b&v > 0 {
|
||||||
|
(*crc)++
|
||||||
|
}
|
||||||
|
if xor {
|
||||||
|
(*crc) ^= crc9poly
|
||||||
|
}
|
||||||
|
v >>= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func crc9end(crc *uint16, bits int) {
|
||||||
|
for i := 0; i < bits; i++ {
|
||||||
|
xor := (*crc)&0x100 > 0
|
||||||
|
(*crc) <<= 1
|
||||||
|
// Limit the number of shift registers to 9.
|
||||||
|
*crc &= 0x01ff
|
||||||
|
if xor {
|
||||||
|
(*crc) ^= crc9poly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func crc16(crc *uint16, b byte) {
|
||||||
|
var v uint8 = 0x80
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
xor := ((*crc) & 0x8000) > 0
|
||||||
|
(*crc) <<= 1
|
||||||
|
if b&v > 0 {
|
||||||
|
(*crc)++
|
||||||
|
}
|
||||||
|
if xor {
|
||||||
|
(*crc) ^= crc16poly
|
||||||
|
}
|
||||||
|
v >>= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func crc16end(crc *uint16) {
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
xor := ((*crc) & 0x8000) > 0
|
||||||
|
(*crc) <<= 1
|
||||||
|
if xor {
|
||||||
|
(*crc) ^= crc16poly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func crc32(crc *uint32, b byte) {
|
||||||
|
var v uint8 = 0x80
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
xor := ((*crc) & 0x80000000) > 0
|
||||||
|
(*crc) <<= 1
|
||||||
|
if b&v > 0 {
|
||||||
|
(*crc)++
|
||||||
|
}
|
||||||
|
if xor {
|
||||||
|
(*crc) ^= crc32poly
|
||||||
|
}
|
||||||
|
v >>= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func crc32end(crc *uint32) {
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
xor := ((*crc) & 0x80000000) > 0
|
||||||
|
(*crc) <<= 1
|
||||||
|
if xor {
|
||||||
|
(*crc) ^= crc32poly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package dmr
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCRC9(t *testing.T) {
|
||||||
|
tests := map[uint16][]byte{
|
||||||
|
0x0000: []byte{},
|
||||||
|
0x0100: []byte{0x00, 0x01},
|
||||||
|
0x0179: []byte("hello world"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for want, test := range tests {
|
||||||
|
var crc uint16
|
||||||
|
for _, b := range test {
|
||||||
|
crc9(&crc, b, 8)
|
||||||
|
}
|
||||||
|
crc9end(&crc, 8)
|
||||||
|
if crc != want {
|
||||||
|
t.Fatalf("crc9 %v failed: %#04x != %#04x", test, crc, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCRC16(t *testing.T) {
|
||||||
|
tests := map[uint16][]byte{
|
||||||
|
0x0000: []byte{},
|
||||||
|
0x1021: []byte{0x00, 0x01},
|
||||||
|
0x3be4: []byte("hello world"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for want, test := range tests {
|
||||||
|
var crc uint16
|
||||||
|
for _, b := range test {
|
||||||
|
crc16(&crc, b)
|
||||||
|
}
|
||||||
|
crc16end(&crc)
|
||||||
|
if crc != want {
|
||||||
|
t.Fatalf("crc16 %v failed: %#04x != %#04x", test, crc, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCRC32(t *testing.T) {
|
||||||
|
tests := map[uint32][]byte{
|
||||||
|
0x00000000: []byte{},
|
||||||
|
0x04c11db7: []byte{0x00, 0x01},
|
||||||
|
0x737af2ae: []byte("hello world"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for want, test := range tests {
|
||||||
|
var crc uint32
|
||||||
|
for _, b := range test {
|
||||||
|
crc32(&crc, b)
|
||||||
|
}
|
||||||
|
crc32end(&crc)
|
||||||
|
if crc != want {
|
||||||
|
t.Fatalf("crc32 %v failed: %#08x != %#08x", test, crc, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,328 @@
|
|||||||
package dmr
|
package dmr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/text/encoding"
|
||||||
|
"golang.org/x/text/encoding/charmap"
|
||||||
|
"golang.org/x/text/encoding/unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// n_DFragMax, see DMR AI spec. page 163.
|
||||||
|
MaxPacketFragmentSize = 1500
|
||||||
|
)
|
||||||
|
|
||||||
type DataBlock struct {
|
type DataBlock struct {
|
||||||
Serial uint8
|
Serial uint8
|
||||||
CRC uint16
|
CRC uint16
|
||||||
OK bool
|
OK bool
|
||||||
Data [24]byte
|
Data []byte
|
||||||
Length uint8
|
Length uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseDataBlock(data []byte, dataType uint8, confirmed bool) (*DataBlock, error) {
|
||||||
|
var (
|
||||||
|
crc uint16
|
||||||
|
db = &DataBlock{
|
||||||
|
Length: dataBlockLength(dataType, confirmed),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if confirmed {
|
||||||
|
db.Serial = data[0] >> 1
|
||||||
|
db.CRC = uint16(data[0]&B00000001)<<8 | uint16(data[1])
|
||||||
|
db.Data = make([]byte, db.Length)
|
||||||
|
copy(db.Data, data[2:db.Length])
|
||||||
|
|
||||||
|
//log.Debugf("data block serial: %#02x (%#07b), crc: %#02x (%#09b), length: %d, confirmed: %t:", db.Serial, db.Serial, db.CRC, db.CRC, db.Length, confirmed)
|
||||||
|
//log.Debug(hex.Dump(data[2 : 2+db.Length]))
|
||||||
|
|
||||||
|
for _, block := range db.Data {
|
||||||
|
crc9(&crc, block, 8)
|
||||||
|
}
|
||||||
|
crc9(&crc, db.Serial, 7)
|
||||||
|
crc9end(&crc, 8)
|
||||||
|
|
||||||
|
// Inverting according to the inversion polynomial.
|
||||||
|
crc = ^crc
|
||||||
|
crc &= 0x01ff
|
||||||
|
// Applying CRC mask, see DMR AI spec. page 143
|
||||||
|
crc ^= 0x01ff
|
||||||
|
|
||||||
|
// FIXME(pd0mz): this is not working
|
||||||
|
if crc != db.CRC {
|
||||||
|
return nil, fmt.Errorf("dmr: block CRC error (%#04x != %#04x)", crc, db.CRC)
|
||||||
|
}
|
||||||
|
log.Debugf("dmr: data block CRC passed %#04x", crc)
|
||||||
|
} else {
|
||||||
|
copy(db.Data, data[:db.Length])
|
||||||
|
}
|
||||||
|
|
||||||
|
db.OK = true
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DataBlock) Bytes(dataType uint8, confirmed bool) []byte {
|
||||||
|
var (
|
||||||
|
size = dataBlockLength(dataType, confirmed)
|
||||||
|
data = make([]byte, size)
|
||||||
|
)
|
||||||
|
|
||||||
|
if confirmed {
|
||||||
|
for _, block := range db.Data {
|
||||||
|
crc9(&db.CRC, block, 8)
|
||||||
|
}
|
||||||
|
crc9(&db.CRC, db.Serial, 7)
|
||||||
|
crc9end(&db.CRC, 8)
|
||||||
|
|
||||||
|
// Inverting according to the inversion polynomial.
|
||||||
|
db.CRC = ^db.CRC
|
||||||
|
db.CRC &= 0x01ff
|
||||||
|
// Applying CRC mask, see DMR AI spec. page 143
|
||||||
|
db.CRC ^= 0x01ff
|
||||||
|
|
||||||
|
data[0] = (db.Serial << 1) | (uint8(db.CRC>>8) & 0x01)
|
||||||
|
data[1] = uint8(db.CRC)
|
||||||
|
copy(data[2:], db.Data)
|
||||||
|
} else {
|
||||||
|
copy(data, db.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataBlockLength(dataType uint8, confirmed bool) uint8 {
|
||||||
|
var size uint8
|
||||||
|
|
||||||
|
switch dataType {
|
||||||
|
case Data:
|
||||||
|
size = 22
|
||||||
|
break
|
||||||
|
case Rate12Data:
|
||||||
|
size = 10
|
||||||
|
break
|
||||||
|
case Rate34Data:
|
||||||
|
size = 16
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if !confirmed {
|
||||||
|
return size + 2
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataFragment struct {
|
||||||
|
Data []byte
|
||||||
|
Stored int
|
||||||
|
Needed int
|
||||||
|
CRC uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (df *DataFragment) DataBlocks(dataType uint8, confirm bool) ([]*DataBlock, error) {
|
||||||
|
df.Stored = len(df.Data)
|
||||||
|
if df.Stored > MaxPacketFragmentSize {
|
||||||
|
df.Stored = MaxPacketFragmentSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// See DMR AI spec. page. 73. for block sizes.
|
||||||
|
var size = int(dataBlockLength(dataType, confirm))
|
||||||
|
df.Needed = (df.Stored + size - 1) / size
|
||||||
|
|
||||||
|
// Leave enough room for the 4 bytes CRC32
|
||||||
|
if (df.Needed*size)-df.Stored < 4 {
|
||||||
|
df.Needed++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate fragment CRC32
|
||||||
|
for i := 0; i < (df.Needed*size)-4; i += 2 {
|
||||||
|
if i+1 < df.Stored {
|
||||||
|
crc32(&df.CRC, df.Data[i+1])
|
||||||
|
} else {
|
||||||
|
crc32(&df.CRC, 0)
|
||||||
|
}
|
||||||
|
if i < df.Stored {
|
||||||
|
crc32(&df.CRC, df.Data[i])
|
||||||
|
} else {
|
||||||
|
crc32(&df.CRC, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crc32end(&df.CRC)
|
||||||
|
|
||||||
|
var (
|
||||||
|
blocks = make([]*DataBlock, df.Needed)
|
||||||
|
stored int
|
||||||
|
)
|
||||||
|
for i := range blocks {
|
||||||
|
block := &DataBlock{
|
||||||
|
Serial: uint8(i % 128),
|
||||||
|
Length: dataBlockLength(dataType, confirm),
|
||||||
|
}
|
||||||
|
block.Data = make([]byte, block.Length)
|
||||||
|
|
||||||
|
store := int(block.Length)
|
||||||
|
if df.Stored-stored < store {
|
||||||
|
store = df.Stored - store
|
||||||
|
}
|
||||||
|
copy(block.Data, df.Data[stored:stored+store])
|
||||||
|
stored += store
|
||||||
|
|
||||||
|
if i == (df.Needed - 1) {
|
||||||
|
block.Data[block.Length-1] = uint8(df.CRC >> 24)
|
||||||
|
block.Data[block.Length-2] = uint8(df.CRC >> 16)
|
||||||
|
block.Data[block.Length-3] = uint8(df.CRC >> 8)
|
||||||
|
block.Data[block.Length-4] = uint8(df.CRC)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate block CRC9
|
||||||
|
block.CRC = 0
|
||||||
|
for _, b := range block.Data {
|
||||||
|
crc9(&block.CRC, b, 8)
|
||||||
|
}
|
||||||
|
crc9(&block.CRC, block.Serial, 7)
|
||||||
|
crc9end(&block.CRC, 8)
|
||||||
|
|
||||||
|
// Inverting according to the inversion polynomial
|
||||||
|
block.CRC = ^block.CRC
|
||||||
|
block.CRC &= 0x01ff
|
||||||
|
// Applying CRC mask, see DMR AI spec. page 143
|
||||||
|
block.CRC ^= 0x01ff
|
||||||
|
|
||||||
|
blocks[i] = block
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CombineDataBlocks(blocks []*DataBlock) (*DataFragment, error) {
|
||||||
|
if blocks == nil || len(blocks) == 0 {
|
||||||
|
return nil, errors.New("dmr: no data blocks to combine")
|
||||||
|
}
|
||||||
|
|
||||||
|
f := &DataFragment{
|
||||||
|
Data: make([]byte, MaxPacketFragmentSize),
|
||||||
|
}
|
||||||
|
for i, block := range blocks {
|
||||||
|
if block.Length == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i < (len(blocks) - 1) {
|
||||||
|
if f.Stored+int(block.Length) < len(f.Data) {
|
||||||
|
copy(f.Data[f.Stored:], block.Data[:block.Length])
|
||||||
|
f.Stored += int(block.Length)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if f.Stored+int(block.Length)-4 < len(f.Data) {
|
||||||
|
copy(f.Data[f.Stored:], block.Data[:block.Length])
|
||||||
|
f.Stored += int(block.Length)
|
||||||
|
}
|
||||||
|
f.CRC = 0
|
||||||
|
f.CRC |= uint32(block.Data[block.Length-4])
|
||||||
|
f.CRC |= uint32(block.Data[block.Length-3]) << 8
|
||||||
|
f.CRC |= uint32(block.Data[block.Length-2]) << 16
|
||||||
|
f.CRC |= uint32(block.Data[block.Length-1]) << 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var crc uint32
|
||||||
|
for i := 0; i < f.Stored-4; i += 2 {
|
||||||
|
crc32(&crc, f.Data[i+1])
|
||||||
|
crc32(&crc, f.Data[i])
|
||||||
|
}
|
||||||
|
crc32end(&crc)
|
||||||
|
|
||||||
|
if crc != f.CRC {
|
||||||
|
return nil, fmt.Errorf("dmr: fragment CRC error (%#08x != %#08x)", crc, f.CRC)
|
||||||
|
}
|
||||||
|
log.Debugf("dmr: data fragment CRC passed %#08x", crc)
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var encodingMap map[uint8]encoding.Encoding
|
||||||
|
|
||||||
|
func BuildMessageData(msg string, ddFormat uint8, nullTerminated bool) ([]byte, error) {
|
||||||
|
if e, ok := encodingMap[ddFormat]; ok {
|
||||||
|
enc := e.NewEncoder()
|
||||||
|
data, err := enc.Bytes([]byte(msg))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if nullTerminated {
|
||||||
|
data = append(data, []byte{0x00, 0x00}...)
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("dmr: encoding %s text is not supported", DDFormatName[ddFormat])
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseMessageData(data []byte, ddFormat uint8, nullTerminated bool) (string, error) {
|
||||||
|
if e, ok := encodingMap[ddFormat]; ok {
|
||||||
|
dec := e.NewDecoder()
|
||||||
|
str, err := dec.Bytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if nullTerminated {
|
||||||
|
if idx := bytes.IndexByte(str, 0x00); idx >= 0 {
|
||||||
|
str = str[:idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(str), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("dmr: decoding %s text is not supported", DDFormatName[ddFormat])
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
/*
|
||||||
|
DDFormatBinary: "binary",
|
||||||
|
DDFormatBCD: "BCD",
|
||||||
|
DDFormat7BitChar: "7-bit characters",
|
||||||
|
DDFormat8BitISO8859_1: "8-bit ISO 8859-1",
|
||||||
|
DDFormat8BitISO8859_2: "8-bit ISO 8859-2",
|
||||||
|
DDFormat8BitISO8859_3: "8-bit ISO 8859-3",
|
||||||
|
DDFormat8BitISO8859_4: "8-bit ISO 8859-4",
|
||||||
|
DDFormat8BitISO8859_5: "8-bit ISO 8859-5",
|
||||||
|
DDFormat8BitISO8859_6: "8-bit ISO 8859-6",
|
||||||
|
DDFormat8BitISO8859_7: "8-bit ISO 8859-7",
|
||||||
|
DDFormat8BitISO8859_8: "8-bit ISO 8859-8",
|
||||||
|
DDFormat8BitISO8859_9: "8-bit ISO 8859-9",
|
||||||
|
DDFormat8BitISO8859_10: "8-bit ISO 8859-10",
|
||||||
|
DDFormat8BitISO8859_11: "8-bit ISO 8859-11",
|
||||||
|
DDFormat8BitISO8859_13: "8-bit ISO 8859-13",
|
||||||
|
DDFormat8BitISO8859_14: "8-bit ISO 8859-14",
|
||||||
|
DDFormat8BitISO8859_15: "8-bit ISO 8859-15",
|
||||||
|
DDFormat8BitISO8859_16: "8-bit ISO 8859-16",
|
||||||
|
DDFormatUTF8: "UTF-8",
|
||||||
|
DDFormatUTF16: "UTF-16",
|
||||||
|
DDFormatUTF16BE: "UTF-16 big endian",
|
||||||
|
DDFormatUTF16LE: "UTF-16 little endian",
|
||||||
|
DDFormatUTF32: "UTF-32",
|
||||||
|
DDFormatUTF32BE: "UTF-32 big endian",
|
||||||
|
DDFormatUTF32LE: "UTF-32 little endian",
|
||||||
|
*/
|
||||||
|
encodingMap = map[uint8]encoding.Encoding{
|
||||||
|
DDFormatBinary: binaryEncoding{},
|
||||||
|
DDFormat8BitISO8859_2: charmap.ISO8859_2,
|
||||||
|
DDFormat8BitISO8859_3: charmap.ISO8859_3,
|
||||||
|
DDFormat8BitISO8859_4: charmap.ISO8859_4,
|
||||||
|
DDFormat8BitISO8859_5: charmap.ISO8859_5,
|
||||||
|
DDFormat8BitISO8859_6: charmap.ISO8859_6,
|
||||||
|
DDFormat8BitISO8859_7: charmap.ISO8859_7,
|
||||||
|
DDFormat8BitISO8859_8: charmap.ISO8859_8,
|
||||||
|
DDFormat8BitISO8859_10: charmap.ISO8859_10,
|
||||||
|
DDFormat8BitISO8859_13: charmap.ISO8859_13,
|
||||||
|
DDFormat8BitISO8859_14: charmap.ISO8859_14,
|
||||||
|
DDFormat8BitISO8859_15: charmap.ISO8859_15,
|
||||||
|
DDFormat8BitISO8859_16: charmap.ISO8859_16,
|
||||||
|
DDFormatUTF8: unicode.UTF8,
|
||||||
|
DDFormatUTF16: unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM),
|
||||||
|
DDFormatUTF16BE: unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM),
|
||||||
|
DDFormatUTF16LE: unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package dmr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/text/encoding"
|
||||||
|
"golang.org/x/text/transform"
|
||||||
|
)
|
||||||
|
|
||||||
|
type binaryCoder struct{ transform.NopResetter }
|
||||||
|
|
||||||
|
func (e binaryCoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
|
// The decoder can only make the input larger, not smaller.
|
||||||
|
n := len(src)
|
||||||
|
if len(dst) < n {
|
||||||
|
err = transform.ErrShortDst
|
||||||
|
n = len(dst)
|
||||||
|
atEOF = false
|
||||||
|
} else {
|
||||||
|
copy(dst[:n], src)
|
||||||
|
nDst = n
|
||||||
|
nSrc = n
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type binaryEncoding struct{}
|
||||||
|
|
||||||
|
func (e binaryEncoding) NewDecoder() *encoding.Decoder {
|
||||||
|
return &encoding.Decoder{Transformer: binaryCoder{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e binaryEncoding) NewEncoder() *encoding.Encoder {
|
||||||
|
return &encoding.Encoder{Transformer: binaryCoder{}}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package dmr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDataBlock(t *testing.T) {
|
||||||
|
want := &DataBlock{
|
||||||
|
Serial: 123,
|
||||||
|
Data: []byte{0x17, 0x2a},
|
||||||
|
Length: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
data := want.Bytes(Rate34Data, true)
|
||||||
|
if data == nil {
|
||||||
|
t.Fatal("encode failed")
|
||||||
|
}
|
||||||
|
size := int(dataBlockLength(Rate34Data, true))
|
||||||
|
if len(data) != size {
|
||||||
|
t.Fatalf("encode failed: expected %d bytes, got %d", size, len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoding is tested in the DataFragment test
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataFragment(t *testing.T) {
|
||||||
|
msg, err := BuildMessageData("CQCQCQ PD0MZ", DDFormatUTF16, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("build message failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &DataFragment{Data: msg}
|
||||||
|
blocks, err := want.DataBlocks(Rate34Data, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("encode failed: %v", err)
|
||||||
|
}
|
||||||
|
if blocks == nil {
|
||||||
|
t.Fatal("encode failed: blocks is nil")
|
||||||
|
}
|
||||||
|
if len(blocks) != 2 {
|
||||||
|
t.Fatalf("encode failed: expected 2 blocks, got %d", len(blocks))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, block := range blocks {
|
||||||
|
t.Log(fmt.Sprintf("block %02d:\n%s", i, hex.Dump(block.Data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
test, err := CombineDataBlocks(blocks)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decode failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(test.Data[:len(want.Data)], want.Data) {
|
||||||
|
t.Log(fmt.Sprintf("want:\n%s", hex.Dump(want.Data)))
|
||||||
|
t.Log(fmt.Sprintf("got:\n%s", hex.Dump(test.Data)))
|
||||||
|
t.Fatal("decode failed: data is wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessage(t *testing.T) {
|
||||||
|
msg := "CQCQCQ PD0MZ"
|
||||||
|
|
||||||
|
var encodings = []int{}
|
||||||
|
for i := range encodingMap {
|
||||||
|
encodings = append(encodings, int(i))
|
||||||
|
}
|
||||||
|
sort.Sort(sort.IntSlice(encodings))
|
||||||
|
|
||||||
|
for _, i := range encodings {
|
||||||
|
e := encodingMap[uint8(i)]
|
||||||
|
n := DDFormatName[uint8(i)]
|
||||||
|
t.Logf("testing %s encoding", n)
|
||||||
|
|
||||||
|
enc := e.NewDecoder()
|
||||||
|
str, err := enc.String(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error encoding to %s: %v", n, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := e.NewDecoder()
|
||||||
|
out, err := dec.String(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error decoding from %s: %v", n, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(fmt.Sprintf("encoder:\n%s", hex.Dump([]byte(str))))
|
||||||
|
t.Log(fmt.Sprintf("decoder:\n%s", hex.Dump([]byte(out))))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
package dmr
|
@ -1,2 +1,6 @@
|
|||||||
// Package dmr implements various protocols for interfacing Digital Mobile Radio repeaters and base stations.
|
// Package dmr implements various protocols for interfacing Digital Mobile Radio repeaters and base stations.
|
||||||
package dmr
|
package dmr
|
||||||
|
|
||||||
|
import "github.com/op/go-logging"
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("dmr/terminal")
|
||||||
|
Loading…
Reference in New Issue