You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
365 lines
8.2 KiB
Go
365 lines
8.2 KiB
Go
package dmr
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pd0mz/go-dmr/crc/quadres_16_7"
|
|
"github.com/pd0mz/go-dmr/fec"
|
|
)
|
|
|
|
// Priority Levels
|
|
const (
|
|
NoPriority uint8 = iota
|
|
Priority1
|
|
Priority2
|
|
Priority3
|
|
)
|
|
|
|
// PriorityName is a map of priority level to string.
|
|
var PriorityName = map[uint8]string{
|
|
NoPriority: "no priority",
|
|
Priority1: "priority 1",
|
|
Priority2: "priority 2",
|
|
Priority3: "priority 3",
|
|
}
|
|
|
|
// ServiceOptions as per DMR part 2, section 7.2.1.
|
|
type ServiceOptions struct {
|
|
// Emergency service
|
|
Emergency bool
|
|
// Not defined in document
|
|
Privacy bool
|
|
// Broadcast service (only defined in group calls)
|
|
Broadcast bool
|
|
// Open Voice Call Mode
|
|
OpenVoiceCallMode bool
|
|
// Priority 3 (0b11) is the highest priority
|
|
Priority uint8
|
|
}
|
|
|
|
// Byte packs the service options to a single byte.
|
|
func (so *ServiceOptions) Byte() byte {
|
|
var b byte
|
|
if so.Emergency {
|
|
b |= B00000001
|
|
}
|
|
if so.Privacy {
|
|
b |= B00000010
|
|
}
|
|
if so.Broadcast {
|
|
b |= B00010000
|
|
}
|
|
if so.OpenVoiceCallMode {
|
|
b |= B00100000
|
|
}
|
|
b |= (so.Priority << 6)
|
|
return b
|
|
}
|
|
|
|
// String representatation of the service options.
|
|
func (so *ServiceOptions) String() string {
|
|
var part = []string{}
|
|
if so.Emergency {
|
|
part = append(part, "emergency")
|
|
}
|
|
if so.Privacy {
|
|
part = append(part, "privacy")
|
|
}
|
|
if so.Broadcast {
|
|
part = append(part, "broadcast")
|
|
}
|
|
if so.OpenVoiceCallMode {
|
|
part = append(part, "Open Voice Call Mode")
|
|
}
|
|
part = append(part, fmt.Sprintf("%s (%d)", PriorityName[so.Priority], so.Priority))
|
|
return strings.Join(part, ", ")
|
|
}
|
|
|
|
// ParseServiceOptions parses the service options byte.
|
|
func ParseServiceOptions(data byte) ServiceOptions {
|
|
return ServiceOptions{
|
|
Emergency: (data & B00000001) > 0,
|
|
Privacy: (data & B00000010) > 0,
|
|
Broadcast: (data & B00010000) > 0,
|
|
OpenVoiceCallMode: (data & B00100000) > 0,
|
|
Priority: (data & B11000000) >> 6,
|
|
}
|
|
}
|
|
|
|
// Full Link Control Opcode
|
|
const (
|
|
GroupVoiceChannelUser uint8 = 0x00 // B000000
|
|
UnitToUnitVoiceChannelUser uint8 = 0x03 // B000011
|
|
)
|
|
|
|
// LC is a Link Control message.
|
|
type LC struct {
|
|
CallType uint8
|
|
Opcode uint8
|
|
FeatureSetID uint8
|
|
ServiceOptions ServiceOptions
|
|
DstID uint32
|
|
SrcID uint32
|
|
}
|
|
|
|
// Bytes packs the Link Control message to bytes.
|
|
func (lc *LC) Bytes() []byte {
|
|
var fclo uint8
|
|
switch lc.CallType {
|
|
case CallTypeGroup:
|
|
fclo = GroupVoiceChannelUser
|
|
break
|
|
case CallTypePrivate:
|
|
fclo = UnitToUnitVoiceChannelUser
|
|
break
|
|
}
|
|
|
|
return []byte{
|
|
fclo,
|
|
lc.FeatureSetID,
|
|
lc.ServiceOptions.Byte(),
|
|
uint8(lc.DstID >> 16),
|
|
uint8(lc.DstID >> 8),
|
|
uint8(lc.DstID),
|
|
uint8(lc.SrcID >> 16),
|
|
uint8(lc.SrcID >> 8),
|
|
uint8(lc.SrcID),
|
|
}
|
|
}
|
|
|
|
func (lc *LC) String() string {
|
|
return fmt.Sprintf("call type %s, feature set id %d, %d->%d, service options %s",
|
|
CallTypeName[lc.CallType], lc.FeatureSetID, lc.SrcID, lc.DstID, lc.ServiceOptions.String())
|
|
}
|
|
|
|
// ParseLC parses a packed Link Control message.
|
|
func ParseLC(data []byte) (*LC, error) {
|
|
if data == nil {
|
|
return nil, errors.New("dmr/lc: data can't be nil")
|
|
}
|
|
if len(data) != 9 {
|
|
return nil, fmt.Errorf("dmr/lc: expected 9 LC bytes, got %d", len(data))
|
|
}
|
|
|
|
if data[0]&B10000000 > 0 {
|
|
return nil, errors.New("dmr/lc: protect flag is not 0")
|
|
}
|
|
|
|
var (
|
|
ct uint8
|
|
fclo = data[0] & B00111111
|
|
)
|
|
switch fclo {
|
|
case GroupVoiceChannelUser:
|
|
ct = CallTypeGroup
|
|
break
|
|
case UnitToUnitVoiceChannelUser:
|
|
ct = CallTypePrivate
|
|
break
|
|
default:
|
|
return nil, fmt.Errorf("dmr/lc: unknown FCLO %06b (%d)", fclo, fclo)
|
|
}
|
|
|
|
return &LC{
|
|
CallType: ct,
|
|
FeatureSetID: data[1],
|
|
ServiceOptions: ParseServiceOptions(data[2]),
|
|
DstID: uint32(data[3])<<16 | uint32(data[4])<<8 | uint32(data[5]),
|
|
SrcID: uint32(data[6])<<16 | uint32(data[7])<<8 | uint32(data[8]),
|
|
}, nil
|
|
}
|
|
|
|
// ParseFullLC parses a packed Link Control message and checks/corrects the Reed-Solomon check data.
|
|
func ParseFullLC(data []byte) (*LC, error) {
|
|
if data == nil {
|
|
return nil, errors.New("dmr/full lc: data can't be nil")
|
|
}
|
|
if len(data) != 12 {
|
|
return nil, fmt.Errorf("dmr/full lc: expected 12 bytes, got %d", len(data))
|
|
}
|
|
|
|
syndrome := &fec.RS_12_9_Poly{}
|
|
if err := fec.RS_12_9_CalcSyndrome(data, syndrome); err != nil {
|
|
return nil, err
|
|
}
|
|
if !fec.RS_12_9_CheckSyndrome(syndrome) {
|
|
if _, err := fec.RS_12_9_Correct(data, syndrome); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return ParseLC(data[:9])
|
|
}
|
|
|
|
// EMB LCSS fragments.
|
|
const (
|
|
SingleFragment uint8 = iota
|
|
FirstFragment
|
|
LastFragment
|
|
Continuation
|
|
)
|
|
|
|
// LCSSName is a map of LCSS fragment type to string.
|
|
var LCSSName = map[uint8]string{
|
|
SingleFragment: "single fragment",
|
|
FirstFragment: "first fragment",
|
|
LastFragment: "last fragment",
|
|
Continuation: "continuation",
|
|
}
|
|
|
|
// EMB contains embedded signalling.
|
|
type EMB struct {
|
|
ColorCode uint8
|
|
LCSS uint8
|
|
}
|
|
|
|
func (emb *EMB) String() string {
|
|
return fmt.Sprintf("color code %d, %s (%d)", emb.ColorCode, LCSSName[emb.LCSS], emb.LCSS)
|
|
}
|
|
|
|
// ParseEMB parses embedded signalling
|
|
func ParseEMB(bits []byte) (*EMB, error) {
|
|
if len(bits) != EMBBits {
|
|
return nil, fmt.Errorf("dmr/emb: expected %d bits, got %d", EMBBits, len(bits))
|
|
}
|
|
|
|
if !quadres_16_7.Check(bits) {
|
|
return nil, errors.New("dmr/emb: checksum error")
|
|
}
|
|
|
|
if bits[4] != 0 {
|
|
return nil, errors.New("dmr/emb: pi is not 0")
|
|
}
|
|
|
|
return &EMB{
|
|
ColorCode: uint8(bits[0])<<3 | uint8(bits[1])<<2 | uint8(bits[2])<<1 | uint8(bits[3]),
|
|
LCSS: uint8(bits[5])<<1 | uint8(bits[6]),
|
|
}, nil
|
|
}
|
|
|
|
// ParseEMBBitsFromSync extracts the embedded signalling bits from the SYNC bits.
|
|
func ParseEMBBitsFromSync(sync []byte) ([]byte, error) {
|
|
if sync == nil {
|
|
return nil, errors.New("dmr/emb from sync: bits can't be nil")
|
|
}
|
|
if len(sync) != 48 {
|
|
return nil, fmt.Errorf("dmr/emb from sync: expected 48 sync bits, got %d", len(sync))
|
|
}
|
|
|
|
var bits = make([]byte, 16)
|
|
copy(bits[:8], sync[:8])
|
|
copy(bits[8:], sync[8+32:])
|
|
return bits, nil
|
|
}
|
|
|
|
// ParseEmbeddedSignallingLCFromSyncBits extracts the embedded signalling LC from the SYNC bits.
|
|
func ParseEmbeddedSignallingLCFromSyncBits(sync []byte) ([]byte, error) {
|
|
if sync == nil {
|
|
return nil, errors.New("dmr/emb lc from sync: bits can't be nil")
|
|
}
|
|
if len(sync) != 48 {
|
|
return nil, fmt.Errorf("dmr/emb lc from sync: expected 48 sync bits, got %d", len(sync))
|
|
}
|
|
|
|
var bits = make([]byte, 32)
|
|
copy(bits, sync[8:40])
|
|
return bits, nil
|
|
}
|
|
|
|
// EmbeddedSignallingLC contains the embedded signalling LC and checksum.
|
|
type EmbeddedSignallingLC struct {
|
|
Bits []byte
|
|
Checksum []byte
|
|
}
|
|
|
|
// Check verifies the checksum in the embedded signalling LC.
|
|
func (eslc *EmbeddedSignallingLC) Check() bool {
|
|
var checksum uint8
|
|
checksum |= eslc.Checksum[0] << 4
|
|
checksum |= eslc.Checksum[1] << 3
|
|
checksum |= eslc.Checksum[2] << 2
|
|
checksum |= eslc.Checksum[3] << 1
|
|
checksum |= eslc.Checksum[4] << 0
|
|
|
|
var data = BitsToBytes(eslc.Bits)
|
|
var verify uint16
|
|
for _, b := range data {
|
|
verify += uint16(b)
|
|
}
|
|
|
|
var calculated = uint8(verify % 31)
|
|
return calculated == checksum
|
|
}
|
|
|
|
// Interleave packs the embedded signalling LC to interleaved bits.
|
|
func (eslc *EmbeddedSignallingLC) Interleave() []byte {
|
|
var bits = make([]byte, 77)
|
|
var j int
|
|
for i := range bits {
|
|
switch i {
|
|
case 32:
|
|
bits[i] = eslc.Checksum[0]
|
|
break
|
|
case 43:
|
|
bits[i] = eslc.Checksum[1]
|
|
break
|
|
case 54:
|
|
bits[i] = eslc.Checksum[2]
|
|
break
|
|
case 65:
|
|
bits[i] = eslc.Checksum[3]
|
|
break
|
|
case 76:
|
|
bits[i] = eslc.Checksum[4]
|
|
break
|
|
default:
|
|
bits[i] = eslc.Bits[j]
|
|
j++
|
|
}
|
|
}
|
|
|
|
return bits
|
|
}
|
|
|
|
// DeinterleaveEmbeddedSignallingLC deinterleaves the embedded signalling LC bits.
|
|
func DeinterleaveEmbeddedSignallingLC(bits []byte) (*EmbeddedSignallingLC, error) {
|
|
if bits == nil {
|
|
return nil, errors.New("dmr/emb lc deinterleave: bits can't be nil")
|
|
}
|
|
if len(bits) != 77 {
|
|
return nil, fmt.Errorf("dmr/emb lc deinterleave: expected 77 bits, got %d", len(bits))
|
|
}
|
|
|
|
var eslc = &EmbeddedSignallingLC{
|
|
Bits: make([]byte, 72),
|
|
Checksum: make([]byte, 5),
|
|
}
|
|
var j int
|
|
for i, b := range bits {
|
|
switch i {
|
|
case 32:
|
|
eslc.Checksum[0] = b
|
|
break
|
|
case 43:
|
|
eslc.Checksum[1] = b
|
|
break
|
|
case 54:
|
|
eslc.Checksum[2] = b
|
|
break
|
|
case 65:
|
|
eslc.Checksum[3] = b
|
|
break
|
|
case 76:
|
|
eslc.Checksum[4] = b
|
|
break
|
|
default:
|
|
eslc.Bits[j] = b
|
|
j++
|
|
}
|
|
}
|
|
|
|
return eslc, nil
|
|
}
|