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