Added lots of stuff

pull/1/head
Wijnand Modderman-Lenstra 9 years ago
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…
Cancel
Save