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.

296 lines
7.2 KiB
Go

package dmr
import (
"errors"
"fmt"
"strings"
)
// Control Block Opcode
const (
OutboundActivationOpcode = B00111000
UnitToUnitVoiceServiceRequestOpcode = B00000100
UnitToUnitVoiceServiceAnswerResponseOpcode = B00000101
NegativeAcknowledgeResponseOpcode = B00100100
PreambleOpcode = B00111101
)
type ControlBlock struct {
CRC uint16
Last bool
Opcode uint8
SrcID, DstID uint32
Data ControlBlockData
}
func (cb *ControlBlock) Bytes() ([]byte, error) {
var data = make([]byte, InfoSize)
if err := cb.Data.Write(data); err != nil {
return nil, err
}
if cb.Last {
data[0] |= B10000000
}
data[4] = uint8(cb.DstID >> 16)
data[5] = uint8(cb.DstID >> 8)
data[6] = uint8(cb.DstID)
data[7] = uint8(cb.SrcID >> 16)
data[8] = uint8(cb.SrcID >> 8)
data[9] = uint8(cb.SrcID)
// Calculate CRC16
for i := 0; i < 10; i++ {
crc16(&cb.CRC, data[i])
}
crc16end(&cb.CRC)
// Inverting according to the inversion polynomial.
cb.CRC = ^cb.CRC
// Applying CRC mask, see DMR AI spec. page 143.
cb.CRC ^= 0xa5a5
data[10] = uint8(cb.CRC >> 8)
data[11] = uint8(cb.CRC)
return data, nil
}
func (cb *ControlBlock) String() string {
if cb.Data == nil {
return fmt.Sprintf("CSBK, last %t, %d->%d, unknown (opcode %d)",
cb.Last, cb.SrcID, cb.DstID, cb.Opcode)
}
return fmt.Sprintf("CSBK, last %t, %d->%d, %s (opcode %d)",
cb.Last, cb.SrcID, cb.DstID, cb.Data.String(), cb.Opcode)
}
type ControlBlockData interface {
String() string
Write([]byte) error
Parse([]byte) error
}
type OutboundActivation struct{}
func (d *OutboundActivation) String() string { return "outbound activation" }
func (d *OutboundActivation) Parse(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
return nil
}
func (d *OutboundActivation) Write(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
data[0] |= OutboundActivationOpcode
return nil
}
type UnitToUnitVoiceServiceRequest struct {
Options uint8
}
func (d *UnitToUnitVoiceServiceRequest) String() string {
return fmt.Sprintf("unit to unit voice service request, options %d", d.Options)
}
func (d *UnitToUnitVoiceServiceRequest) Parse(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
d.Options = data[2]
return nil
}
func (d *UnitToUnitVoiceServiceRequest) Write(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
data[0] |= UnitToUnitVoiceServiceRequestOpcode
data[2] = d.Options
return nil
}
var _ (ControlBlockData) = (*UnitToUnitVoiceServiceRequest)(nil)
type UnitToUnitVoiceServiceAnswerResponse struct {
Options uint8
Response uint8
}
func (d *UnitToUnitVoiceServiceAnswerResponse) String() string {
return fmt.Sprintf("unit to unit voice service answer response, options %d, response %d", d.Options, d.Response)
}
func (d *UnitToUnitVoiceServiceAnswerResponse) Parse(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
d.Options = data[2]
d.Response = data[3]
return nil
}
func (d *UnitToUnitVoiceServiceAnswerResponse) Write(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
data[0] |= UnitToUnitVoiceServiceAnswerResponseOpcode
data[2] = d.Options
data[3] = d.Response
return nil
}
var _ (ControlBlockData) = (*UnitToUnitVoiceServiceAnswerResponse)(nil)
type NegativeAcknowledgeResponse struct {
SourceType bool
ServiceType uint8
Reason uint8
}
func (d *NegativeAcknowledgeResponse) String() string {
return fmt.Sprintf("negative ACK response, source %t, service %d, reason %d", d.SourceType, d.ServiceType, d.Reason)
}
func (d *NegativeAcknowledgeResponse) Parse(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
d.SourceType = (data[2] & B01000000) > 0
d.ServiceType = (data[2] & B00011111)
d.Reason = data[3]
return nil
}
func (d *NegativeAcknowledgeResponse) Write(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
data[0] |= NegativeAcknowledgeResponseOpcode
data[2] = d.ServiceType
if d.SourceType {
data[2] |= B01000000
}
data[3] = d.Reason
return nil
}
var _ (ControlBlockData) = (*NegativeAcknowledgeResponse)(nil)
type Preamble struct {
DataFollows bool
DstIsGroup bool
Blocks uint8
}
func (d *Preamble) String() string {
var part = []string{"preamble"}
if d.DataFollows {
part = append(part, "data folllows")
}
if d.DstIsGroup {
part = append(part, "group")
} else {
part = append(part, "unit")
}
part = append(part, fmt.Sprintf("%d blocks", d.Blocks))
return strings.Join(part, ", ")
}
func (d *Preamble) Parse(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
d.DataFollows = (data[2] & B10000000) > 0
d.DstIsGroup = (data[2] & B01000000) > 0
d.Blocks = data[3]
return nil
}
func (d *Preamble) Write(data []byte) error {
if len(data) != InfoSize {
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
data[0] |= PreambleOpcode
if d.DataFollows {
data[2] |= B10000000
}
if d.DstIsGroup {
data[2] |= B01000000
}
data[3] = d.Blocks
return nil
}
var _ (ControlBlockData) = (*Preamble)(nil)
func ParseControlBlock(data []byte) (*ControlBlock, error) {
if len(data) != InfoSize {
return nil, fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
}
// Calculate CRC16
var crc uint16
for i := 0; i < 10; i++ {
crc16(&crc, data[i])
}
crc16end(&crc)
// Inverting according to the inversion polynomial
crc = ^crc
// Applying CRC mask, see DMR AI spec. page 143.
crc ^= 0xa5a5
// Check packet
if data[0]&B01000000 > 0 {
return nil, errors.New("dmr: CSBK protect flag is set")
}
if data[1] != 0 {
return nil, errors.New("dmr: CSBK feature set ID is set")
}
cb := &ControlBlock{
CRC: uint16(data[10])<<8 | uint16(data[11]),
Last: (data[0] & B10000000) > 0,
Opcode: (data[0] & B00111111),
DstID: uint32(data[4])<<16 | uint32(data[5])<<8 | uint32(data[6]),
SrcID: uint32(data[7])<<16 | uint32(data[8])<<8 | uint32(data[9]),
}
if crc != cb.CRC {
return nil, fmt.Errorf("dmr: control block CRC error (%#04x != %#04x)", crc, cb.CRC)
}
switch cb.Opcode {
case OutboundActivationOpcode:
cb.Data = &OutboundActivation{}
break
case UnitToUnitVoiceServiceRequestOpcode:
cb.Data = &UnitToUnitVoiceServiceRequest{}
break
case UnitToUnitVoiceServiceAnswerResponseOpcode:
cb.Data = &UnitToUnitVoiceServiceAnswerResponse{}
break
case NegativeAcknowledgeResponseOpcode:
cb.Data = &NegativeAcknowledgeResponse{}
break
case PreambleOpcode:
cb.Data = &Preamble{}
break
default:
return nil, fmt.Errorf("dmr: unknown CSBK opcode %02x (%06b)", cb.Opcode, cb.Opcode)
}
if err := cb.Data.Parse(data); err != nil {
return nil, err
}
return cb, nil
}