Cleanup
This commit is contained in:
parent
b7c07ac7b3
commit
f9646e9667
4 changed files with 202 additions and 92 deletions
65
homebrew/data.go
Normal file
65
homebrew/data.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package homebrew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pd0mz/go-dmr/bit"
|
||||||
|
"github.com/pd0mz/go-dmr/ipsc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseData reads a raw DMR data frame from the homebrew protocol and parses it as it were an IPSC packet.
|
||||||
|
func ParseData(data []byte) (*ipsc.Packet, error) {
|
||||||
|
if len(data) != 53 {
|
||||||
|
return nil, fmt.Errorf("invalid packet length %d, expected 53 bytes", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
slotType uint16
|
||||||
|
dataType = (data[15] & bit.B11110000) >> 4
|
||||||
|
frameType = (data[15] & bit.B00001100) >> 2
|
||||||
|
)
|
||||||
|
switch frameType {
|
||||||
|
case bit.B00000000, bit.B00000001: // voice/voice sync
|
||||||
|
switch dataType {
|
||||||
|
case bit.B00000000:
|
||||||
|
slotType = ipsc.VoiceDataA
|
||||||
|
case bit.B00000001:
|
||||||
|
slotType = ipsc.VoiceDataB
|
||||||
|
case bit.B00000010:
|
||||||
|
slotType = ipsc.VoiceDataC
|
||||||
|
case bit.B00000011:
|
||||||
|
slotType = ipsc.VoiceDataD
|
||||||
|
case bit.B00000100:
|
||||||
|
slotType = ipsc.VoiceDataE
|
||||||
|
case bit.B00000101:
|
||||||
|
slotType = ipsc.VoiceDataF
|
||||||
|
}
|
||||||
|
case bit.B00000010: // data sync
|
||||||
|
switch dataType {
|
||||||
|
case bit.B00000001:
|
||||||
|
slotType = ipsc.VoiceLCHeader
|
||||||
|
case bit.B00000010:
|
||||||
|
slotType = ipsc.TerminatorWithLC
|
||||||
|
case bit.B00000011:
|
||||||
|
slotType = ipsc.CSBK
|
||||||
|
case bit.B00000110:
|
||||||
|
slotType = ipsc.DataHeader
|
||||||
|
case bit.B00000111:
|
||||||
|
slotType = ipsc.Rate12Data
|
||||||
|
case bit.B00001000:
|
||||||
|
slotType = ipsc.Rate34Data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ipsc.Packet{
|
||||||
|
Timeslot: (data[15] & bit.B00000001),
|
||||||
|
CallType: (data[15] & bit.B00000010) >> 1,
|
||||||
|
FrameType: (data[15] & bit.B00001100) >> 2,
|
||||||
|
SlotType: slotType,
|
||||||
|
SrcID: uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]),
|
||||||
|
DstID: uint32(data[8])<<16 | uint32(data[9])<<8 | uint32(data[10]),
|
||||||
|
Payload: data[20:],
|
||||||
|
PayloadBits: bit.NewBits(data[20:]),
|
||||||
|
Sequence: data[4],
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ package homebrew
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -14,6 +13,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pd0mz/go-dmr/ipsc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -97,89 +98,8 @@ const (
|
||||||
UnitCall
|
UnitCall
|
||||||
)
|
)
|
||||||
|
|
||||||
// FrameType reflects the DMR data frame type.
|
|
||||||
type FrameType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
Voice FrameType = iota
|
|
||||||
VoiceSync
|
|
||||||
DataSync
|
|
||||||
UnusedFrameType
|
|
||||||
)
|
|
||||||
|
|
||||||
// Frame is a frame of DMR data.
|
|
||||||
type Frame struct {
|
|
||||||
Signature [4]byte
|
|
||||||
Sequence byte
|
|
||||||
SrcID uint32
|
|
||||||
DstID uint32
|
|
||||||
RepeaterID uint32
|
|
||||||
Flags byte
|
|
||||||
StreamID uint32
|
|
||||||
DMR [33]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes packs the frame into bytes ready to send over the network.
|
|
||||||
func (f *Frame) Bytes() []byte {
|
|
||||||
var b = make([]byte, 53)
|
|
||||||
copy(b[:4], f.Signature[:])
|
|
||||||
b[4] = f.Sequence
|
|
||||||
b[5] = byte(f.SrcID >> 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataType is the slot type when the FrameType is a data sync frame. For voice
|
|
||||||
// frames it's the voice sequence number in DMR numbering, where 0=A, 1=B, etc.
|
|
||||||
func (f *Frame) DataType() byte {
|
|
||||||
return f.Flags >> 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// FrameType returns if the current frame is a voice, voice sync or data sync frame.
|
|
||||||
func (f *Frame) FrameType() FrameType {
|
|
||||||
return FrameType((f.Flags >> 2) & 0x03)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slot is the time slot number.
|
|
||||||
func (f *Frame) Slot() int {
|
|
||||||
return int(f.Flags&0x01) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFrame parses a raw DMR data frame.
|
|
||||||
func ParseFrame(data []byte) (*Frame, error) {
|
|
||||||
if len(data) != 53 {
|
|
||||||
return nil, errors.New("invalid packet length")
|
|
||||||
}
|
|
||||||
|
|
||||||
f := &Frame{}
|
|
||||||
copy(f.Signature[:], data[:4])
|
|
||||||
f.Sequence = data[4]
|
|
||||||
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])
|
|
||||||
copy(f.DMR[:], data[20:])
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamFunc is called by the DMR repeater when a DMR data frame is received.
|
// StreamFunc is called by the DMR repeater when a DMR data frame is received.
|
||||||
type StreamFunc func(*Frame)
|
type StreamFunc func(*ipsc.Packet)
|
||||||
|
|
||||||
type authStatus byte
|
type authStatus byte
|
||||||
|
|
||||||
|
@ -444,19 +364,32 @@ func (l *Link) parse(queue <-chan packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case authDone:
|
case authDone:
|
||||||
|
if l.Dump {
|
||||||
|
fmt.Printf("received from %s:\n", p.addr)
|
||||||
|
fmt.Print(hex.Dump(p.data))
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case bytes.Equal(p.data[:4], DMRData):
|
case bytes.Equal(p.data[:4], DMRData):
|
||||||
if l.stream == nil {
|
l.parseDMR(p.data)
|
||||||
return
|
|
||||||
}
|
|
||||||
frame, err := ParseFrame(p.data)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error parsing DMR data: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.stream(frame)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Link) parseDMR(d []byte) {
|
||||||
|
// If we have no stream callback, don't even bother to decode the DMR data frame.
|
||||||
|
if l.stream == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
p *ipsc.Packet
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if p, err = ParseData(d); err != nil {
|
||||||
|
log.Printf("dmr/homebrew: error parsing DMRD frame: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.stream(p)
|
||||||
|
}
|
||||||
|
|
105
ipsc/packet.go
Normal file
105
ipsc/packet.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package ipsc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pd0mz/go-dmr/bit"
|
||||||
|
"github.com/pd0mz/go-dmr/dmr"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VoiceLCHeader uint16 = 0x1111
|
||||||
|
TerminatorWithLC uint16 = 0x2222
|
||||||
|
CSBK uint16 = 0x3333
|
||||||
|
DataHeader uint16 = 0x4444
|
||||||
|
Rate12Data uint16 = 0x5555
|
||||||
|
Rate34Data uint16 = 0x6666
|
||||||
|
VoiceDataA uint16 = 0xbbbb
|
||||||
|
VoiceDataB uint16 = 0xcccc
|
||||||
|
VoiceDataC uint16 = 0x7777
|
||||||
|
VoiceDataD uint16 = 0x8888
|
||||||
|
VoiceDataE uint16 = 0x9999
|
||||||
|
VoiceDataF uint16 = 0xaaaa
|
||||||
|
IPSCSync uint16 = 0xeeee
|
||||||
|
UnknownSlotType uint16 = 0x0000
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CallTypePrivate uint8 = iota
|
||||||
|
CallTypeGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TimeslotName = map[uint8]string{
|
||||||
|
0x00: "TS1",
|
||||||
|
0x01: "TS2",
|
||||||
|
}
|
||||||
|
FrameTypeName = map[uint8]string{
|
||||||
|
0x00: "voice",
|
||||||
|
0x01: "voice sync",
|
||||||
|
0x02: "data sync",
|
||||||
|
0x03: "unused",
|
||||||
|
}
|
||||||
|
SlotTypeName = map[uint16]string{
|
||||||
|
0x0000: "unknown",
|
||||||
|
0x1111: "voice LC header",
|
||||||
|
0x2222: "terminator with LC",
|
||||||
|
0x3333: "CBSK",
|
||||||
|
0x4444: "data header",
|
||||||
|
0x5555: "rate 1/2 data",
|
||||||
|
0x6666: "rate 3/4 data",
|
||||||
|
0x7777: "voice data C",
|
||||||
|
0x8888: "voice data D",
|
||||||
|
0x9999: "voice data E",
|
||||||
|
0xaaaa: "voice data F",
|
||||||
|
0xbbbb: "voice data A",
|
||||||
|
0xcccc: "voice data B",
|
||||||
|
0xeeee: "IPSC sync",
|
||||||
|
}
|
||||||
|
CallTypeName = map[uint8]string{
|
||||||
|
0x00: "private",
|
||||||
|
0x01: "group",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Packet struct {
|
||||||
|
Timeslot uint8 // 0=ts1, 1=ts2
|
||||||
|
FrameType uint8
|
||||||
|
SlotType uint16
|
||||||
|
CallType uint8 // 0=private, 1=group
|
||||||
|
SrcID uint32
|
||||||
|
DstID uint32
|
||||||
|
Payload []byte // 34 bytes
|
||||||
|
PayloadBits bit.Bits // 264 bits
|
||||||
|
Sequence uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Packet) Dump() string {
|
||||||
|
var s string
|
||||||
|
s += fmt.Sprintf("timeslot..: 0b%02b (%s)\n", p.Timeslot, TimeslotName[p.Timeslot])
|
||||||
|
s += fmt.Sprintf("frame type: 0b%02b (%s)\n", p.FrameType, FrameTypeName[p.FrameType])
|
||||||
|
s += fmt.Sprintf("slot type.: 0x%04x (%s)\n", p.SlotType, SlotTypeName[p.SlotType])
|
||||||
|
s += fmt.Sprintf("call type.: 0x%02x (%s)\n", p.CallType, CallTypeName[p.CallType])
|
||||||
|
s += fmt.Sprintf("source....: %d\n", p.SrcID)
|
||||||
|
s += fmt.Sprintf("target....: %d\n", p.DstID)
|
||||||
|
s += fmt.Sprintf("payload...: %d bytes (swapped):\n", len(p.Payload))
|
||||||
|
s += hex.Dump(p.Payload)
|
||||||
|
s += fmt.Sprintf("payload...: %d bits:\n", len(p.PayloadBits))
|
||||||
|
s += p.PayloadBits.Dump()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Packet) InfoBits() bit.Bits {
|
||||||
|
var b = make(bit.Bits, dmr.InfoBits)
|
||||||
|
copy(b[0:dmr.InfoHalfBits], p.PayloadBits[0:dmr.InfoHalfBits])
|
||||||
|
copy(b[dmr.InfoHalfBits:], p.PayloadBits[dmr.InfoHalfBits+dmr.SlotTypeBits+dmr.SignalBits:])
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Packet) VoiceBits() bit.Bits {
|
||||||
|
var b = make(bit.Bits, dmr.VoiceBits)
|
||||||
|
copy(b[:dmr.VoiceHalfBits], p.PayloadBits[:dmr.VoiceHalfBits])
|
||||||
|
copy(b[dmr.VoiceHalfBits:], p.PayloadBits[dmr.VoiceHalfBits+dmr.SignalBits:])
|
||||||
|
return b
|
||||||
|
}
|
7
ipsc/payload.go
Normal file
7
ipsc/payload.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package ipsc
|
||||||
|
|
||||||
|
func SwapPayloadBytes(payload []byte) {
|
||||||
|
for i := 0; i < len(payload)-1; i += 2 {
|
||||||
|
payload[i], payload[i+1] = payload[i+1], payload[i]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue