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 }