Added lots of stuff
parent
f5ce7276bd
commit
b3e1434b25
@ -0,0 +1,22 @@
|
||||
// Package dmr contains generic message structures for the Digital Mobile Radio standard
|
||||
package dmr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pd0mz/go-dmr/bit"
|
||||
)
|
||||
|
||||
// Burst contains data from a single burst, see 4.2.2 Burst and frame structure
|
||||
type Burst struct {
|
||||
bits bit.Bits
|
||||
}
|
||||
|
||||
func NewBurst(raw []byte) (*Burst, error) {
|
||||
if len(raw)*8 != PayloadBits {
|
||||
return nil, fmt.Errorf("dmr: expected %d bits, got %d", PayloadBits, len(raw)*8)
|
||||
}
|
||||
b := &Burst{}
|
||||
b.bits = bit.NewBits(raw)
|
||||
return b, nil
|
||||
}
|
@ -0,0 +1,362 @@
|
||||
package dmr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pd0mz/go-dmr/bit"
|
||||
)
|
||||
|
||||
// Data Header Packet Format
|
||||
const (
|
||||
PacketFormatUDT uint8 = iota // 0b0000
|
||||
PacketFormatResponse // 0b0001
|
||||
PacketFormatUnconfirmedData // 0b0010
|
||||
PacketFormatConfirmedData // 0b0011
|
||||
_ // 0b0100
|
||||
_ // 0b0101
|
||||
_ // 0b0110
|
||||
_ // 0b0111
|
||||
_ // 0b1000
|
||||
_ // 0b1001
|
||||
_ // 0b1010
|
||||
_ // 0b1011
|
||||
_ // 0b1100
|
||||
PacketFormatShortDataDefined // 0b1101
|
||||
PacketFormatShortDataRaw // 0b1110
|
||||
PacketFormatProprietaryData // 0b1111
|
||||
)
|
||||
|
||||
// Response Data Header Response Type
|
||||
const (
|
||||
ResponseTypeACK uint8 = iota
|
||||
ResponseTypeIllegalFormat
|
||||
ResponseTypePacketCRCFailed
|
||||
ResponseTypeMemoryFull
|
||||
ResponseTypeRecvFSVNOutOfSeq
|
||||
ResponseTypeUndeliverable
|
||||
ResponseTypeRecvPktOutOfSeq
|
||||
ResponseTypeDisallowed
|
||||
ResponseTypeSelectiveACK
|
||||
)
|
||||
|
||||
var ResponseTypeName = map[uint8]string{
|
||||
ResponseTypeACK: "ACK",
|
||||
ResponseTypeIllegalFormat: "illegal format",
|
||||
ResponseTypePacketCRCFailed: "packet CRC failed",
|
||||
ResponseTypeMemoryFull: "memory full",
|
||||
ResponseTypeRecvFSVNOutOfSeq: "recv FSN out of sequence",
|
||||
ResponseTypeUndeliverable: "undeliverable",
|
||||
ResponseTypeRecvPktOutOfSeq: "recv PKT our of sequence",
|
||||
ResponseTypeDisallowed: "disallowed",
|
||||
ResponseTypeSelectiveACK: "selective ACK",
|
||||
}
|
||||
|
||||
// UDP Response Header UDT Format
|
||||
const (
|
||||
UDTFormatBinary uint8 = iota
|
||||
UDTFormatMSAddress
|
||||
UDTFormat4BitBCD
|
||||
UDTFormatISO_7BitChars
|
||||
UDTFormatISO_8BitChars
|
||||
UDTFormatNMEALocation
|
||||
UDTFormatIPAddress
|
||||
UDTFormat16BitUnicodeChars
|
||||
UDTFormatCustomCodeD1
|
||||
UDTFormatCustomCodeD2
|
||||
)
|
||||
|
||||
var UDTFormatName = map[uint8]string{
|
||||
UDTFormatBinary: "binary",
|
||||
UDTFormatMSAddress: "MS address",
|
||||
UDTFormat4BitBCD: "4-bit BCD",
|
||||
UDTFormatISO_7BitChars: "ISO 7-bit characters",
|
||||
UDTFormatISO_8BitChars: "ISO 8-bit characters",
|
||||
UDTFormatNMEALocation: "NMEA location",
|
||||
UDTFormatIPAddress: "IP address",
|
||||
UDTFormat16BitUnicodeChars: "16-bit Unicode characters",
|
||||
UDTFormatCustomCodeD1: "custom code D1",
|
||||
UDTFormatCustomCodeD2: "custom code D2",
|
||||
}
|
||||
|
||||
// UDT Response Header DD Format
|
||||
const (
|
||||
DDFormatBinary uint8 = iota
|
||||
DDFormatBCD
|
||||
DDFormat7BitChar
|
||||
DDFormat8BitISO8859_1
|
||||
DDFormat8BitISO8859_2
|
||||
DDFormat8BitISO8859_3
|
||||
DDFormat8BitISO8859_4
|
||||
DDFormat8BitISO8859_5
|
||||
DDFormat8BitISO8859_6
|
||||
DDFormat8BitISO8859_7
|
||||
DDFormat8BitISO8859_8
|
||||
DDFormat8BitISO8859_9
|
||||
DDFormat8BitISO8859_10
|
||||
DDFormat8BitISO8859_11
|
||||
DDFormat8BitISO8859_13
|
||||
DDFormat8BitISO8859_14
|
||||
DDFormat8BitISO8859_15
|
||||
DDFormat8BitISO8859_16
|
||||
DDFormatUTF8
|
||||
DDFormatUTF16
|
||||
DDFormatUTF16BE
|
||||
DDFormatUTF16LE
|
||||
DDFormatUTF32
|
||||
DDFormatUTF32BE
|
||||
DDFormatUTF32LE
|
||||
)
|
||||
|
||||
var DDFormatName = map[uint8]string{
|
||||
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",
|
||||
}
|
||||
|
||||
type DataHeader interface {
|
||||
CommonHeader() DataHeaderCommon
|
||||
}
|
||||
|
||||
type DataHeaderCommon struct {
|
||||
PacketFormat uint8
|
||||
DstIsGroup bool
|
||||
ResponseRequested bool
|
||||
ServiceAccessPoint uint8
|
||||
DstID uint32
|
||||
SrcID uint32
|
||||
CRC uint16
|
||||
}
|
||||
|
||||
func (h *DataHeaderCommon) Parse(header []byte) error {
|
||||
h.PacketFormat = header[0] & 0xf
|
||||
h.DstIsGroup = (header[0] & 0x80) > 0
|
||||
h.ResponseRequested = (header[0] & 0x40) > 0
|
||||
h.ServiceAccessPoint = (header[1] & 0xf0) >> 4
|
||||
h.DstID = uint32(header[2])<<16 | uint32(header[3])<<8 | uint32(header[4])
|
||||
h.SrcID = uint32(header[5])<<16 | uint32(header[6])<<8 | uint32(header[7])
|
||||
return nil
|
||||
}
|
||||
|
||||
type UDTDataHeader struct {
|
||||
Common DataHeaderCommon
|
||||
Format uint8
|
||||
PadNibble uint8
|
||||
AppendedBlocks uint8
|
||||
SupplementaryFlag bool
|
||||
OPCode uint8
|
||||
}
|
||||
|
||||
func (h UDTDataHeader) CommonHeader() DataHeaderCommon { return h.Common }
|
||||
|
||||
type UnconfirmedDataHeader struct {
|
||||
Common DataHeaderCommon
|
||||
PadOctetCount uint8
|
||||
FullMessage bool
|
||||
BlocksToFollow uint8
|
||||
FragmentSequenceNumber uint8
|
||||
}
|
||||
|
||||
func (h UnconfirmedDataHeader) CommonHeader() DataHeaderCommon { return h.Common }
|
||||
|
||||
type ConfirmedDataHeader struct {
|
||||
Common DataHeaderCommon
|
||||
PadOctetCount uint8
|
||||
FullMessage bool
|
||||
BlocksToFollow uint8
|
||||
Resync bool
|
||||
SendSequenceNumber uint8
|
||||
FragmentSequenceNumber uint8
|
||||
}
|
||||
|
||||
func (h ConfirmedDataHeader) CommonHeader() DataHeaderCommon { return h.Common }
|
||||
|
||||
type ResponseDataHeader struct {
|
||||
Common DataHeaderCommon
|
||||
BlocksToFollow uint8
|
||||
Class uint8
|
||||
Type uint8
|
||||
Status uint8
|
||||
}
|
||||
|
||||
func (h ResponseDataHeader) CommonHeader() DataHeaderCommon { return h.Common }
|
||||
|
||||
type ProprietaryDataHeader struct {
|
||||
Common DataHeaderCommon
|
||||
ManufacturerID uint8
|
||||
}
|
||||
|
||||
func (h ProprietaryDataHeader) CommonHeader() DataHeaderCommon { return h.Common }
|
||||
|
||||
type ShortDataRawDataHeader struct {
|
||||
Common DataHeaderCommon
|
||||
AppendedBlocks uint8
|
||||
SrcPort uint8
|
||||
DstPort uint8
|
||||
Resync bool
|
||||
FullMessage bool
|
||||
BitPadding uint8
|
||||
}
|
||||
|
||||
func (h ShortDataRawDataHeader) CommonHeader() DataHeaderCommon { return h.Common }
|
||||
|
||||
type ShortDataDefinedDataHeader struct {
|
||||
Common DataHeaderCommon
|
||||
AppendedBlocks uint8
|
||||
DDFormat uint8
|
||||
Resync bool
|
||||
FullMessage bool
|
||||
BitPadding uint8
|
||||
}
|
||||
|
||||
func (h ShortDataDefinedDataHeader) CommonHeader() DataHeaderCommon { return h.Common }
|
||||
|
||||
func ParseDataHeader(header []byte, proprietary bool) (DataHeader, error) {
|
||||
if len(header) != 12 {
|
||||
return nil, fmt.Errorf("header must be 12 bytes, got %d", len(header))
|
||||
}
|
||||
var (
|
||||
ccrc = (uint16(header[10]) << 8) | uint16(header[11])
|
||||
hcrc = dataHeaderCRC(header)
|
||||
)
|
||||
if ccrc != hcrc {
|
||||
return nil, fmt.Errorf("header CRC mismatch, %#04x != %#04x", ccrc, hcrc)
|
||||
}
|
||||
|
||||
if proprietary {
|
||||
return ProprietaryDataHeader{
|
||||
Common: DataHeaderCommon{
|
||||
ServiceAccessPoint: (header[0] & bit.B11110000) >> 4,
|
||||
PacketFormat: (header[0] & bit.B00001111),
|
||||
CRC: ccrc,
|
||||
},
|
||||
ManufacturerID: header[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
common := DataHeaderCommon{
|
||||
CRC: ccrc,
|
||||
}
|
||||
if err := common.Parse(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch common.PacketFormat {
|
||||
case PacketFormatUDT:
|
||||
return UDTDataHeader{
|
||||
Common: common,
|
||||
Format: (header[1] & bit.B00001111),
|
||||
PadNibble: (header[8] & bit.B11111000) >> 3,
|
||||
AppendedBlocks: (header[8] & bit.B00000011),
|
||||
SupplementaryFlag: (header[9] & bit.B10000000) > 0,
|
||||
OPCode: (header[9] & bit.B00111111),
|
||||
}, nil
|
||||
case PacketFormatResponse:
|
||||
return ResponseDataHeader{
|
||||
Common: common,
|
||||
BlocksToFollow: (header[8] & bit.B01111111),
|
||||
Class: (header[9] & bit.B11000000) >> 6,
|
||||
Type: (header[9] & bit.B00111000) >> 3,
|
||||
Status: (header[9] & bit.B00000111),
|
||||
}, nil
|
||||
case PacketFormatUnconfirmedData:
|
||||
return UnconfirmedDataHeader{
|
||||
Common: common,
|
||||
PadOctetCount: (header[0] & bit.B00010000) | (header[1] & bit.B00001111),
|
||||
FullMessage: (header[8] & bit.B10000000) > 0,
|
||||
BlocksToFollow: (header[8] & bit.B01111111),
|
||||
FragmentSequenceNumber: (header[9] & bit.B00001111),
|
||||
}, nil
|
||||
case PacketFormatConfirmedData:
|
||||
return ConfirmedDataHeader{
|
||||
Common: common,
|
||||
PadOctetCount: (header[0] & bit.B00010000) | (header[1] & bit.B00001111),
|
||||
FullMessage: (header[8] & bit.B10000000) > 0,
|
||||
BlocksToFollow: (header[8] & bit.B01111111),
|
||||
Resync: (header[9] & bit.B10000000) > 0,
|
||||
SendSequenceNumber: (header[9] & bit.B01110000) >> 4,
|
||||
FragmentSequenceNumber: (header[9] & bit.B00001111),
|
||||
}, nil
|
||||
case PacketFormatShortDataRaw:
|
||||
return ShortDataRawDataHeader{
|
||||
Common: common,
|
||||
AppendedBlocks: (header[0] & bit.B00110000) | (header[1] & bit.B00001111),
|
||||
SrcPort: (header[8] & bit.B11100000) >> 5,
|
||||
DstPort: (header[8] & bit.B00011100) >> 2,
|
||||
Resync: (header[8] & bit.B00000010) > 0,
|
||||
FullMessage: (header[8] & bit.B00000001) > 0,
|
||||
BitPadding: (header[9]),
|
||||
}, nil
|
||||
case PacketFormatShortDataDefined:
|
||||
return ShortDataDefinedDataHeader{
|
||||
Common: common,
|
||||
AppendedBlocks: (header[0] & bit.B00110000) | (header[1] & bit.B00001111),
|
||||
DDFormat: (header[8] & bit.B11111100) >> 2,
|
||||
Resync: (header[8] & bit.B00000010) > 0,
|
||||
FullMessage: (header[8] & bit.B00000001) > 0,
|
||||
BitPadding: (header[9]),
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("dmr: unknown data header packet format %#02x (%d)", common.PacketFormat, common.PacketFormat)
|
||||
}
|
||||
}
|
||||
|
||||
func dataHeaderCRC(header []byte) uint16 {
|
||||
var crc uint16
|
||||
if len(header) < 10 {
|
||||
return crc
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
crc16(&crc, header[i])
|
||||
}
|
||||
crc16end(&crc)
|
||||
|
||||
return (^crc) ^ 0xcccc
|
||||
}
|
||||
|
||||
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) ^= 0x1021
|
||||
}
|
||||
v >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
func crc16end(crc *uint16) {
|
||||
for i := 0; i < 16; i++ {
|
||||
xor := ((*crc) & 0x8000) > 0
|
||||
(*crc) <<= 1
|
||||
if xor {
|
||||
(*crc) ^= 0x1021
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package dmr
|
||||
|
||||
const (
|
||||
PayloadBits = 98 + 10 + 48 + 10 + 98
|
||||
PayloadSize = 33
|
||||
InfoHalfBits = 98
|
||||
InfoBits = 2 * InfoHalfBits
|
||||
SlotTypeHalfBits = 10
|
||||
SlotTypeBits = 2 * SlotTypeHalfBits
|
||||
SignalBits = 48
|
||||
SyncBits = SignalBits
|
||||
VoiceHalfBits = 108
|
||||
VoiceBits = 2 * VoiceHalfBits
|
||||
EMBHalfBits = 8
|
||||
EMBBits = 2 * EMBHalfBits
|
||||
EMBSignallingLCFragmentBits = 32
|
||||
)
|
@ -0,0 +1,49 @@
|
||||
package dmr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/pd0mz/go-dmr/bit"
|
||||
"github.com/pd0mz/go-dmr/crc/quadres_16_7"
|
||||
)
|
||||
|
||||
// EMB LCSS fragments
|
||||
const (
|
||||
SingleFragment uint8 = iota
|
||||
FirstFragment
|
||||
LastFragment
|
||||
Continuation
|
||||
)
|
||||
|
||||
func ExtractEMBBitsFromSyncBits(sync bit.Bits) bit.Bits {
|
||||
var b = make(bit.Bits, EMBBits)
|
||||
var o = EMBHalfBits + EMBSignallingLCFragmentBits
|
||||
copy(b[:EMBHalfBits], sync[:EMBHalfBits])
|
||||
copy(b[EMBHalfBits:], sync[o:o+EMBHalfBits])
|
||||
return b
|
||||
}
|
||||
|
||||
type EMB struct {
|
||||
ColorCode uint8
|
||||
LCSS uint8
|
||||
}
|
||||
|
||||
func ParseEMB(bits bit.Bits) (*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
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package dmr
|
||||
|
||||
import "github.com/pd0mz/go-dmr/bit"
|
||||
|
||||
func ExtractInfoBits(payload bit.Bits) bit.Bits {
|
||||
var b = make(bit.Bits, InfoBits)
|
||||
copy(b[:InfoHalfBits], payload[:InfoHalfBits])
|
||||
copy(b[InfoHalfBits:], payload[InfoHalfBits+SignalBits+SlotTypeBits:])
|
||||
return b
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
// Package dmr contains generic message structures for the Digital Mobile Radio standard
|
||||
package dmr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pd0mz/go-dmr/bit"
|
||||
)
|
||||
|
||||
const (
|
||||
InfoPartBits = 98
|
||||
InfoBits = InfoPartBits * 2
|
||||
SlotPartBits = 10
|
||||
SlotBits = SlotPartBits * 2
|
||||
PayloadPartBits = InfoPartBits + SlotPartBits
|
||||
PayloadBits = PayloadPartBits * 2
|
||||
SignalBits = 48
|
||||
BurstBits = PayloadBits + SignalBits
|
||||
)
|
||||
|
||||
// Table 9.2: SYNC Patterns
|
||||
var SYNCPatterns = map[string]struct {
|
||||
ControlMode string
|
||||
PDU string
|
||||
}{
|
||||
"\x07\x05\x05\x0f\x0d\x07\x0d\x0f\x07\x05\x0f\x07": {"BS sourced", "voice"},
|
||||
"\x0d\x0f\x0f\x05\x07\x0d\x07\x05\x0d\x0f\x05\x0d": {"BS sourced", "data"},
|
||||
"\x07\x0f\x07\x0d\x05\x0d\x0d\x05\x07\x0d\x0f\x0d": {"MS sourced", "voice"},
|
||||
"\x0d\x05\x0d\x07\x0f\x07\x07\x0f\x0d\x07\x05\x07": {"MS sourced", "data"},
|
||||
"\x07\x07\x0d\x05\x05\x0f\x07\x0d\x0f\x0d\x07\x07": {"MS sourced", "rc sync"},
|
||||
"\x05\x0d\x05\x07\x07\x0f\x07\x07\x05\x07\x0f\x0f": {"TDMA direct mode time slot 1", "voice"},
|
||||
"\x0f\x07\x0f\x0d\x0d\x05\x0d\x0d\x0f\x0d\x05\x05": {"TDMA direct mode time slot 1", "data"},
|
||||
"\x07\x0d\x0f\x0f\x0d\x05\x0f\x05\x05\x0d\x05\x0f": {"TDMA direct mode time slot 2", "voice"},
|
||||
"\x0d\x07\x05\x05\x07\x0f\x05\x0f\x0f\x07\x0f\x05": {"TDMA direct mode time slot 2", "data"},
|
||||
"\x0d\x0d\x07\x0f\x0f\x05\x0d\x07\x05\x07\x0d\x0d": {"Reserved SYNC pattern", "reserved"},
|
||||
}
|
||||
|
||||
// Burst contains data from a single burst, see 4.2.2 Burst and frame structure
|
||||
type Burst struct {
|
||||
bits bit.Bits
|
||||
}
|
||||
|
||||
func NewBurst(raw []byte) (*Burst, error) {
|
||||
if len(raw)*8 != BurstBits {
|
||||
return nil, fmt.Errorf("dmr: expected %d bits, got %d", BurstBits, len(raw)*8)
|
||||
}
|
||||
b := &Burst{}
|
||||
b.bits = bit.NewBits(raw)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Info returns the 196 bits of info in the burst. The data is usually BPTC(196, 96) encoded.
|
||||
func (b *Burst) Info() bit.Bits {
|
||||
// The info is contained in bits 0..98 and 166..216 for a total of 196 bits
|
||||
var n = make(bit.Bits, InfoBits)
|
||||
copy(n[0:InfoPartBits], b.bits[0:InfoPartBits])
|
||||
copy(n[InfoPartBits:InfoBits], b.bits[InfoPartBits+SignalBits+SlotBits:BurstBits])
|
||||
return n
|
||||
}
|
||||
|
||||
// Payload returns the 216 bits of payload in the burst.
|
||||
func (b *Burst) Payload() bit.Bits {
|
||||
// The payload is contained in bits 0..108 and 156..264 for a total of 216 bits
|
||||
var p = make(bit.Bits, PayloadBits)
|
||||
copy(p[0:PayloadPartBits], b.bits[0:PayloadPartBits])
|
||||
copy(p[PayloadPartBits:PayloadBits], b.bits[PayloadPartBits+SignalBits:BurstBits])
|
||||
return p
|
||||
}
|
||||
|
||||
// Signal returns the 48 bits of signal or SYNC information in the burst.
|
||||
func (b *Burst) Signal() bit.Bits {
|
||||
// The signal bits are contained in bits 108..156 for a total of 48 bits
|
||||
var s = make(bit.Bits, SignalBits)
|
||||
copy(s, b.bits[PayloadPartBits:PayloadPartBits+SignalBits])
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *Burst) SlotType() uint32 {
|
||||
/* The slottype is 20 bits, starting after the payload info */
|
||||
var s uint32
|
||||
for i := InfoPartBits; i < InfoPartBits+SlotBits; i++ {
|
||||
var shift = uint32(20 - (i - InfoPartBits))
|
||||
s = s | uint32(b.bits[i]<<shift)
|
||||
}
|
||||
return s
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package repeater
|
||||
|
||||
import (
|
||||
"github.com/pd0mz/go-dmr/bptc"
|
||||
"github.com/pd0mz/go-dmr/dmr"
|
||||
"github.com/pd0mz/go-dmr/ipsc"
|
||||
)
|
||||
|
||||
func (r *Repeater) HandleDataHeader(p *ipsc.Packet) error {
|
||||
var (
|
||||
h dmr.DataHeader
|
||||
err error
|
||||
payload = make([]byte, 12)
|
||||
)
|
||||
|
||||
if err = bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if h, err = dmr.ParseDataHeader(payload, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(maze): handle receiving of data blocks
|
||||
switch h.(type) {
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package repeater
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/pd0mz/go-dmr/bit"
|
||||
"github.com/pd0mz/go-dmr/bptc"
|
||||
"github.com/pd0mz/go-dmr/dmr"
|
||||
"github.com/pd0mz/go-dmr/fec"
|
||||
"github.com/pd0mz/go-dmr/ipsc"
|
||||
)
|
||||
|
||||
type LC struct {
|
||||
CallType uint8
|
||||
DstID, SrcID uint32
|
||||
}
|
||||
|
||||
func (r *Repeater) HandleTerminatorWithLC(p *ipsc.Packet) error {
|
||||
r.DataCallEnd(p)
|
||||
|
||||
var (
|
||||
err error
|
||||
payload = make([]byte, 12)
|
||||
)
|
||||
if err = bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// CRC mask to the checksum. See DMR AI. spec. page 143.
|
||||
payload[9] ^= 0x99
|
||||
payload[10] ^= 0x99
|
||||
payload[11] ^= 0x99
|
||||
|
||||
var lc *LC
|
||||
if lc, err = r.lcDecodeFullLC(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf(" lc: %d -> %d\n", lc.SrcID, lc.DstID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repeater) HandleVoiceLCHeader(p *ipsc.Packet) error {
|
||||
r.DataCallEnd(p)
|
||||
|
||||
var (
|
||||
err error
|
||||
payload = make([]byte, 12)
|
||||
)
|
||||
if err = bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// CRC mask to the checksum. See DMR AI. spec. page 143
|
||||
for i := 9; i < 12; i++ {
|
||||
payload[i] ^= 0x99
|
||||
}
|
||||
|
||||
var lc *LC
|
||||
if lc, err = r.lcDecodeFullLC(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf(" lc: %d -> %d\n", lc.SrcID, lc.DstID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repeater) lcDecode(payload []byte) (*LC, error) {
|
||||
if payload[0]&bit.B10000000 > 0 {
|
||||
return nil, errors.New("dmr/lc: protect flag is not 0")
|
||||
}
|
||||
if payload[1] != 0 {
|
||||
return nil, errors.New("dmr/lc: feature set ID is not 0")
|
||||
}
|
||||
|
||||
lc := &LC{}
|
||||
switch payload[0] & bit.B00111111 {
|
||||
case 3:
|
||||
lc.CallType = ipsc.CallTypePrivate
|
||||
case 0:
|
||||
lc.CallType = ipsc.CallTypeGroup
|
||||
default:
|
||||
return nil, fmt.Errorf("dmr/lc: invalid FCLO; unknown call type %#02x", payload[0]&bit.B00111111)
|
||||
}
|
||||
|
||||
lc.DstID = uint32(payload[3])<<16 | uint32(payload[4])<<8 | uint32(payload[5])
|
||||
lc.SrcID = uint32(payload[6])<<16 | uint32(payload[7])<<8 | uint32(payload[8])
|
||||
return lc, nil
|
||||
}
|
||||
|
||||
func (r *Repeater) lcDecodeFullLC(payload []byte) (*LC, error) {
|
||||
var (
|
||||
err error
|
||||
syndrome = fec.RS_12_9_Poly{}
|
||||
)
|
||||
if err = fec.RS_12_9_CalcSyndrome(payload, &syndrome); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fec.RS_12_9_CheckSyndrome(&syndrome) {
|
||||
if _, err = fec.RS_12_9_Correct(payload, &syndrome); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return r.lcDecode(payload)
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package repeater
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/pd0mz/go-dmr/ipsc"
|
||||
)
|
||||
|
||||
const (
|
||||
Idle uint8 = iota
|
||||
VoiceCallRunning
|
||||
DataCallRunning
|
||||
)
|
||||
|
||||
type SlotData struct {
|
||||
BlocksReceived uint8
|
||||
BlocksExpected uint8
|
||||
PacketHeaderValid bool
|
||||
}
|
||||
|
||||
type SlotVoice struct {
|
||||
// Last frame number
|
||||
Frame uint8
|
||||
}
|
||||
|
||||
type Slot struct {
|
||||
State uint8
|
||||
LastCallReceived time.Time
|
||||
CallStart time.Time
|
||||
CallEnd time.Time
|
||||
CallType uint8
|
||||
SrcID, DstID uint32
|
||||
Data SlotData
|
||||
Voice SlotVoice
|
||||
LastSequence uint8
|
||||
LastSlotType uint16
|
||||
}
|
||||
|
||||
type Repeater struct {
|
||||
Slot []*Slot
|
||||
}
|
||||
|
||||
func New() *Repeater {
|
||||
r := &Repeater{
|
||||
Slot: make([]*Slot, 2),
|
||||
}
|
||||
r.Slot[0] = &Slot{}
|
||||
r.Slot[1] = &Slot{}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Repeater) DataCallEnd(p *ipsc.Packet) {
|
||||
if p.Timeslot > 1 {
|
||||
return
|
||||
}
|
||||
|
||||
slot := r.Slot[p.Timeslot]
|
||||
if slot.State != DataCallRunning {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("dmr/repeater: data call ended on TS%d, %d -> %d\n", p.Timeslot+1, slot.SrcID, slot.DstID)
|
||||
|
||||
slot.State = Idle
|
||||
slot.CallEnd = time.Now()
|
||||
slot.Data.PacketHeaderValid = false
|
||||
}
|
||||
|
||||
func (r *Repeater) VoiceCallStart(p *ipsc.Packet) {
|
||||
if p.Timeslot > 1 {
|
||||
return
|
||||
}
|
||||
|
||||
slot := r.Slot[p.Timeslot]
|
||||
if slot.State == VoiceCallRunning {
|
||||
r.VoiceCallEnd(p)
|
||||
}
|
||||
|
||||
log.Printf("dmr/repeater: voice call started on TS%d, %d -> %d\n", p.Timeslot+1, slot.SrcID, slot.DstID)
|
||||
slot.CallStart = time.Now()
|
||||
slot.CallType = p.CallType
|
||||
slot.SrcID = p.SrcID
|
||||
slot.DstID = p.DstID
|
||||
slot.State = VoiceCallRunning
|
||||
}
|
||||
|
||||
func (r *Repeater) VoiceCallEnd(p *ipsc.Packet) {
|
||||
if p.Timeslot > 1 {
|
||||
return
|
||||
}
|
||||
|
||||
slot := r.Slot[p.Timeslot]
|
||||
if slot.State != VoiceCallRunning {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("dmr/repeater: voice call ended on TS%d, %d -> %d\n", p.Timeslot+1, slot.SrcID, slot.DstID)
|
||||
|
||||
slot.State = Idle
|
||||
slot.CallEnd = time.Now()
|
||||
}
|
||||
|
||||
func (r *Repeater) Stream(p *ipsc.Packet) {
|
||||
// Kill errneous timeslots here
|
||||
if p.Timeslot > 1 {
|
||||
log.Printf("killed packet with timeslot %d\n", p.Timeslot)
|
||||
return
|
||||
}
|
||||
if p.Sequence == r.Slot[p.Timeslot].LastSequence {
|
||||
return
|
||||
}
|
||||
r.Slot[p.Timeslot].LastSequence = p.Sequence
|
||||
|
||||
var err error
|
||||
|
||||
fmt.Printf("dmr[%d] [%d->%d]: %s: ", p.Sequence, p.SrcID, p.DstID, ipsc.SlotTypeName[p.SlotType])
|
||||
switch p.SlotType {
|
||||
case ipsc.VoiceLCHeader:
|
||||
err = r.HandleVoiceLCHeader(p)
|
||||
case ipsc.TerminatorWithLC:
|
||||
err = r.HandleTerminatorWithLC(p)
|
||||
case ipsc.DataHeader:
|
||||
err = r.HandleDataHeader(p)
|
||||
case ipsc.VoiceDataA, ipsc.VoiceDataB, ipsc.VoiceDataC, ipsc.VoiceDataD, ipsc.VoiceDataE, ipsc.VoiceDataF:
|
||||
// De-duplicate packets, since we could run in merged TS1/2 mode
|
||||
if r.Slot[p.Timeslot].LastSlotType == p.SlotType {
|
||||
fmt.Println("(ignored)")
|
||||
} else {
|
||||
err = r.HandleVoiceData(p)
|
||||
}
|
||||
r.Slot[p.Timeslot].LastSlotType = p.SlotType
|
||||
default:
|
||||
fmt.Println("unhandled")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package repeater
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/pd0mz/go-dmr/bptc"
|
||||
"github.com/pd0mz/go-dmr/dmr"
|
||||
"github.com/pd0mz/go-dmr/ipsc"
|
||||
)
|
||||
|
||||
var voiceFrameMap = map[uint16]uint8{
|
||||
ipsc.VoiceDataA: 0,
|
||||
ipsc.VoiceDataB: 1,
|
||||
ipsc.VoiceDataC: 2,
|
||||
ipsc.VoiceDataD: 3,
|
||||
ipsc.VoiceDataE: 4,
|
||||
ipsc.VoiceDataF: 5,
|
||||
}
|
||||
|
||||
func (r *Repeater) HandleVoiceData(p *ipsc.Packet) error {
|
||||
r.DataCallEnd(p)
|
||||
|
||||
var (
|
||||
err error
|
||||
payload = make([]byte, 12)
|
||||
)
|
||||
|
||||
if err = bptc.Process(dmr.ExtractInfoBits(p.PayloadBits), payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if r.Slot[p.Timeslot].State != VoiceCallRunning {
|
||||
r.VoiceCallStart(p)
|
||||
}
|
||||
|
||||
return r.HandleVoiceFrame(p)
|
||||
}
|
||||
|
||||
func (r *Repeater) HandleVoiceFrame(p *ipsc.Packet) error {
|
||||
// This may contain a sync frame
|
||||
sync := dmr.ExtractSyncBits(p.PayloadBits)
|
||||
patt := dmr.SyncPattern(sync)
|
||||
if patt != dmr.SyncPatternUnknown && r.Slot[p.Timeslot].Voice.Frame != 0 {
|
||||
fmt.Printf("sync pattern %s\n", dmr.SyncPatternName[patt])
|
||||
r.Slot[p.Timeslot].Voice.Frame = 0
|
||||
return nil
|
||||
} else {
|
||||
// This may be a duplicate frame
|
||||
var (
|
||||
oldFrame = r.Slot[p.Timeslot].Voice.Frame
|
||||
newFrame = voiceFrameMap[p.SlotType]
|
||||
)
|
||||
switch {
|
||||
case oldFrame > 5:
|
||||
// Ignore, wait for next sync frame
|
||||
return nil
|
||||
case newFrame == oldFrame:
|
||||
return errors.New("dmr/voice: ignored, duplicate frame")
|
||||
case newFrame > oldFrame:
|
||||
if newFrame-oldFrame > 1 {
|
||||
log.Printf("dmr/voice: framedrop, went from %c -> %c", 'A'+oldFrame, 'A'+newFrame)
|
||||
}
|
||||
case newFrame < oldFrame:
|
||||
if newFrame > 0 {
|
||||
log.Printf("dmr/voice: framedrop, went from %c -> %c", 'A'+oldFrame, 'A'+newFrame)
|
||||
}
|
||||
}
|
||||
r.Slot[p.Timeslot].Voice.Frame++
|
||||
}
|
||||
|
||||
// If it's not a sync frame, then it should have an EMB inside the sync field.
|
||||
var (
|
||||
emb *dmr.EMB
|
||||
err error
|
||||
)
|
||||
if emb, err = dmr.ParseEMB(dmr.ExtractEMBBitsFromSyncBits(sync)); err != nil {
|
||||
fmt.Println("unknown sync pattern, no EMB")
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("EMB LCSS %d\n", emb.LCSS)
|
||||
|
||||
// TODO(maze): implement VBPTC matrix handling
|
||||
switch emb.LCSS {
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package dmr
|
||||
|
||||
import "github.com/pd0mz/go-dmr/bit"
|
||||
|
||||
var SlotTypeName = [16]string{
|
||||
"PI Header", // 0000
|
||||
"VOICE Header:", // 0001
|
||||
"TLC:", // 0010
|
||||
"CSBK:", // 0011
|
||||
"MBC Header:", // 0100
|
||||
"MBC:", // 0101
|
||||
"DATA Header:", // 0110
|
||||
"RATE 1/2 DATA:", // 0111
|
||||
"RATE 3/4 DATA:", // 1000
|
||||
"Slot idle", // 1001
|
||||
"Rate 1 DATA", // 1010
|
||||
"Unknown/Bad (11)", // 1011
|
||||
"Unknown/Bad (12)", // 1100
|
||||
"Unknown/Bad (13)", // 1101
|
||||
"Unknown/Bad (14)", // 1110
|
||||
"Unknown/Bad (15)", // 1111
|
||||
}
|
||||
|
||||
func ExtractSlotType(payload bit.Bits) []byte {
|
||||
bits := ExtractSlotTypeBits(payload)
|
||||
return bits.Bytes()
|
||||
}
|
||||
|
||||
func ExtractSlotTypeBits(payload bit.Bits) bit.Bits {
|
||||
var b = make(bit.Bits, SlotTypeBits)
|
||||
copy(b[:SlotTypeHalfBits], payload[InfoHalfBits:InfoHalfBits+SlotTypeHalfBits])
|
||||
var o = InfoHalfBits + SlotTypeHalfBits + SyncBits
|
||||
copy(b[SlotTypeHalfBits:], payload[o:o+SlotTypeHalfBits])
|
||||
return b
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package dmr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/pd0mz/go-dmr/bit"
|
||||
)
|
||||
|
||||
// Table 9.2: SYNC Patterns
|
||||
const (
|
||||
SyncPatternBSSourcedVoice uint16 = 1 << iota
|
||||
SyncPatternBSSourcedData
|
||||
SyncPatternMSSourcedVoice
|
||||
SyncPatternMSSourcedData
|
||||
SyncPatternMSSourcedRC
|
||||
SyncPatternDirectVoiceTS1
|
||||
SyncPatternDirectDataTS1
|
||||
SyncPatternDirectVoiceTS2
|
||||
SyncPatternDirectDataTS2
|
||||
SyncPatternUnknown
|
||||
)
|
||||
|
||||
var (
|
||||
bsSourcedVoice = []byte{0x75, 0x5f, 0xd7, 0xdf, 0x75, 0xf7}
|
||||
bsSourcedData = []byte{0xdf, 0xf5, 0x7d, 0x75, 0xdf, 0x5d}
|
||||
msSourcedVoice = []byte{0x7f, 0x7d, 0x5d, 0xd5, 0x7d, 0xfd}
|
||||
msSourcedData = []byte{0xd5, 0xd7, 0xf7, 0x7f, 0xd7, 0x57}
|
||||
msSourcedRC = []byte{0x77, 0xd5, 0x5f, 0x7d, 0xfd, 0x77}
|
||||
directVoiceTS1 = []byte{0x5d, 0x57, 0x7f, 0x77, 0x57, 0xff}
|
||||
directDataTS1 = []byte{0xf7, 0xfd, 0xd5, 0xdd, 0xfd, 0x55}
|
||||
directVoiceTS2 = []byte{0x7d, 0xff, 0xd5, 0xf5, 0x5d, 0x5f}
|
||||
directDataTS2 = []byte{0xd7, 0x55, 0x7f, 0x5f, 0xf7, 0xf5}
|
||||
SyncPatternName = map[uint16]string{
|
||||
SyncPatternBSSourcedVoice: "bs sourced voice",
|
||||
SyncPatternBSSourcedData: "bs sourced data",
|
||||
SyncPatternMSSourcedVoice: "ms sourced voice",
|
||||
SyncPatternMSSourcedData: "ms sourced data",
|
||||
SyncPatternMSSourcedRC: "ms sourced rc",
|
||||
SyncPatternDirectVoiceTS1: "direct voice ts1",
|
||||
SyncPatternDirectDataTS1: "direct data ts1",
|
||||
SyncPatternDirectVoiceTS2: "direct voice ts2",
|
||||
SyncPatternDirectDataTS2: "direct data ts2",
|
||||
SyncPatternUnknown: "unknown",
|
||||
}
|
||||
)
|
||||
|
||||
func ExtractSyncBits(payload bit.Bits) bit.Bits {
|
||||
return payload[108:156]
|
||||
}
|
||||
|
||||
func SyncPattern(bits bit.Bits) uint16 {
|
||||
var b = bits.Bytes()
|
||||
switch {
|
||||
case bytes.Equal(b, bsSourcedVoice):
|
||||
return SyncPatternBSSourcedVoice
|
||||
case bytes.Equal(b, bsSourcedData):
|
||||
return SyncPatternBSSourcedData
|
||||
case bytes.Equal(b, msSourcedVoice):
|
||||
return SyncPatternMSSourcedVoice
|
||||
case bytes.Equal(b, msSourcedData):
|
||||
return SyncPatternMSSourcedData
|
||||
case bytes.Equal(b, msSourcedRC):
|
||||
return SyncPatternMSSourcedRC
|
||||
case bytes.Equal(b, directVoiceTS1):
|
||||
return SyncPatternDirectVoiceTS1
|
||||
case bytes.Equal(b, directDataTS1):
|
||||
return SyncPatternDirectDataTS1
|
||||
case bytes.Equal(b, directVoiceTS2):
|
||||
return SyncPatternDirectVoiceTS2
|
||||
case bytes.Equal(b, directDataTS2):
|
||||
return SyncPatternDirectDataTS2
|
||||
default:
|
||||
return SyncPatternUnknown
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue