diff --git a/.gitignore b/.gitignore index daf913b..71e502c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,9 @@ _testmain.go *.exe *.test *.prof + +# Editor remains +*.swp + +# Dump files +*.dump diff --git a/bptc/bptc.go b/bptc/bptc.go index 12fbea6..d5f3972 100644 --- a/bptc/bptc.go +++ b/bptc/bptc.go @@ -4,7 +4,8 @@ package bptc import ( "errors" "fmt" - "pd0mz/go-dmr/bit" + + "github.com/pd0mz/go-dmr/bit" ) type vector [4]bit.Bit diff --git a/dmr/message.go b/dmr/message.go new file mode 100644 index 0000000..cec98ae --- /dev/null +++ b/dmr/message.go @@ -0,0 +1,86 @@ +// 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]<> 16) + b[6] = byte(f.SrcID >> 8) + b[7] = byte(f.SrcID) + b[8] = byte(f.DstID >> 16) + b[9] = byte(f.DstID >> 8) + b[10] = byte(f.DstID) + binary.BigEndian.PutUint32(b[11:15], f.RepeaterID) + b[15] = f.Flags + binary.BigEndian.PutUint32(b[16:20], f.StreamID) + copy(b[21:], f.DMR[:]) + + return b +} + // CallType returns if the current frame is a group or unit call. func (f *Frame) CallType() CallType { return CallType((f.Flags >> 1) & 0x01) @@ -149,8 +168,8 @@ func ParseFrame(data []byte) (*Frame, error) { f := &Frame{} copy(f.Signature[:], data[:4]) f.Sequence = data[4] - f.SrcID = binary.BigEndian.Uint32(append([]byte{0x00}, data[5:7]...)) - f.DstID = binary.BigEndian.Uint32(append([]byte{0x00}, data[8:10]...)) + f.SrcID = (uint32(data[5]) << 16) | (uint32(data[6]) << 8) | uint32(data[7]) + f.DstID = (uint32(data[8]) << 16) | (uint32(data[9]) << 8) | uint32(data[10]) f.RepeaterID = binary.BigEndian.Uint32(data[11:15]) f.Flags = data[15] f.StreamID = binary.BigEndian.Uint32(data[16:20]) @@ -179,6 +198,11 @@ type Network struct { MasterID uint32 } +type packet struct { + addr *net.UDPAddr + data []byte +} + type Link struct { Dump bool config ConfigFunc @@ -250,7 +274,9 @@ func (l *Link) Run() error { return err } + queue := make(chan packet) go l.login() + go l.parse(queue) for { var ( @@ -263,7 +289,7 @@ func (l *Link) Run() error { continue } - go l.parse(peer, data[:n]) + queue <- packet{peer, data[:n]} } return nil @@ -304,7 +330,9 @@ func (l *Link) login() { case authDone: config := l.config().Bytes() - fmt.Printf(hex.Dump(config)) + if l.Dump { + fmt.Printf(hex.Dump(config)) + } log.Printf("dmr/homebrew: logged in, sending %d bytes of repeater configuration\n", len(config)) if err := l.Send(l.master.addr, config); err != nil { @@ -342,88 +370,93 @@ func (l *Link) keepAlive() { } } -func (l *Link) parse(addr *net.UDPAddr, data []byte) { - size := len(data) +func (l *Link) parse(queue <-chan packet) { + for { + select { + case p := <-queue: + size := len(p.data) + if size < 4 { + continue + } - switch l.master.status { - case authNone: - if bytes.Equal(data, DMRData) { - return - } - if size < 14 { - return - } - packet := data[:6] - repeater, err := hex.DecodeString(string(data[6:14])) - if err != nil { - log.Println("dmr/homebrew: unexpected login reply from master") - l.master.status = authFail - break - } + switch l.master.status { + case authNone: + if bytes.Equal(p.data[:4], DMRData) { + return + } + if size < 14 { + return + } + packet := p.data[:6] + repeater, err := hex.DecodeString(string(p.data[6:14])) + if err != nil { + log.Println("dmr/homebrew: unexpected login reply from master") + l.master.status = authFail + break + } - switch { - case bytes.Equal(packet, MasterNAK): - log.Printf("dmr/homebrew: login refused by master %d\n", repeater) - l.master.status = authFail - break - case bytes.Equal(packet, MasterACK): - log.Printf("dmr/homebrew: login accepted by master %d\n", repeater) - l.master.secret = data[14:] - l.master.status = authBegin - break - default: - log.Printf("dmr/homebrew: unexpected login reply from master %d\n", repeater) - l.master.status = authFail - break - } + switch { + case bytes.Equal(packet, MasterNAK): + log.Printf("dmr/homebrew: login refused by master %d\n", repeater) + l.master.status = authFail + break + case bytes.Equal(packet, MasterACK): + log.Printf("dmr/homebrew: login accepted by master %d\n", repeater) + l.master.secret = p.data[14:] + l.master.status = authBegin + break + default: + log.Printf("dmr/homebrew: unexpected login reply from master %d\n", repeater) + l.master.status = authFail + break + } - case authBegin: - if bytes.Equal(data, DMRData) { - return - } - if size < 14 { - log.Println("dmr/homebrew: unexpected login reply from master") - l.master.status = authFail - break - } - packet := data[:6] - repeater, err := hex.DecodeString(string(data[6:14])) - if err != nil { - log.Println("dmr/homebrew: unexpected login reply from master") - l.master.status = authFail - break - } + case authBegin: + if bytes.Equal(p.data[:4], DMRData) { + return + } + if size < 14 { + log.Println("dmr/homebrew: unexpected login reply from master") + l.master.status = authFail + break + } + packet := p.data[:6] + repeater, err := hex.DecodeString(string(p.data[6:14])) + if err != nil { + log.Println("dmr/homebrew: unexpected login reply from master") + l.master.status = authFail + break + } - switch { - case bytes.Equal(packet, MasterNAK): - log.Printf("dmr/homebrew: authentication refused by master %d\n", repeater) - l.master.status = authFail - break - case bytes.Equal(packet, MasterACK): - log.Printf("dmr/homebrew: authentication accepted by master %d\n", repeater) - l.master.status = authDone - break - default: - log.Printf("dmr/homebrew: unexpected authentication reply from master %d\n", repeater) - l.master.status = authFail - break - } + switch { + case bytes.Equal(packet, MasterNAK): + log.Printf("dmr/homebrew: authentication refused by master %d\n", repeater) + l.master.status = authFail + break + case bytes.Equal(packet, MasterACK): + log.Printf("dmr/homebrew: authentication accepted by master %d\n", repeater) + l.master.status = authDone + break + default: + log.Printf("dmr/homebrew: unexpected authentication reply from master %d\n", repeater) + l.master.status = authFail + break + } - case authDone: - if len(data) < 4 { - return - } - switch { - case bytes.Equal(data[:4], DMRData): - if l.stream == nil { - return - } - frame, err := ParseFrame(data) - if err != nil { - log.Printf("error parsing DMR data: %v\n", err) - return + case authDone: + switch { + case bytes.Equal(p.data[:4], DMRData): + if l.stream == nil { + return + } + frame, err := ParseFrame(p.data) + if err != nil { + log.Printf("error parsing DMR data: %v\n", err) + return + } + l.stream(frame) + } } - l.stream(frame) } } }