|
|
|
package terminal
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/op/go-logging"
|
|
|
|
"github.com/pd0mz/go-dmr"
|
|
|
|
"github.com/pd0mz/go-dmr/bptc"
|
|
|
|
"github.com/pd0mz/go-dmr/lc"
|
|
|
|
"github.com/pd0mz/go-dmr/trellis"
|
|
|
|
"github.com/pd0mz/go-dmr/vbptc"
|
|
|
|
)
|
|
|
|
|
|
|
|
var log = logging.MustGetLogger("dmr/terminal")
|
|
|
|
|
|
|
|
const (
|
|
|
|
idle uint8 = iota
|
|
|
|
dataCallActive
|
|
|
|
voiceCallActive
|
|
|
|
)
|
|
|
|
|
|
|
|
const VoiceFrameDuration = time.Millisecond * 60
|
|
|
|
|
|
|
|
type Slot struct {
|
|
|
|
call struct {
|
|
|
|
start time.Time
|
|
|
|
end time.Time
|
|
|
|
}
|
|
|
|
dstID, srcID uint32
|
|
|
|
dataType uint8
|
|
|
|
data struct {
|
|
|
|
packetHeaderValid bool
|
|
|
|
blocks []*dmr.DataBlock
|
|
|
|
blocksExpected int
|
|
|
|
blocksReceived int
|
|
|
|
header *dmr.DataHeader
|
|
|
|
}
|
|
|
|
voice struct {
|
|
|
|
lastFrame uint8
|
|
|
|
streamID uint32
|
|
|
|
}
|
|
|
|
selectiveAckRequestsSent int
|
|
|
|
rxSequence int
|
|
|
|
fullMessageBlocks int
|
|
|
|
embeddedSignalling *vbptc.VBPTC
|
|
|
|
last struct {
|
|
|
|
packetReceived time.Time
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewSlot() *Slot {
|
|
|
|
s := &Slot{}
|
|
|
|
|
|
|
|
// Expecting 8 rows of variable length BPTC coded embedded LC data.
|
|
|
|
// It will contain 77 data bits (without the Hamming (16,11) checksums
|
|
|
|
// and the last row of parity bits).
|
|
|
|
s.embeddedSignalling = vbptc.New(8)
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
type VoiceFrameFunc func(*dmr.Packet, []byte)
|
|
|
|
|
|
|
|
type Terminal struct {
|
|
|
|
ID uint32
|
|
|
|
Call string
|
|
|
|
CallMap map[uint32]string
|
|
|
|
Repeater dmr.Repeater
|
|
|
|
TalkGroup []uint32
|
|
|
|
SoftwareDelay bool
|
|
|
|
|
|
|
|
accept map[uint32]bool
|
|
|
|
slot []*Slot
|
|
|
|
state uint8
|
|
|
|
vff VoiceFrameFunc
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(id uint32, call string, r dmr.Repeater) *Terminal {
|
|
|
|
t := &Terminal{
|
|
|
|
ID: id,
|
|
|
|
Call: call,
|
|
|
|
Repeater: r,
|
|
|
|
slot: []*Slot{NewSlot(), NewSlot(), NewSlot()},
|
|
|
|
accept: map[uint32]bool{id: true},
|
|
|
|
}
|
|
|
|
|
|
|
|
r.SetPacketFunc(t.handlePacket)
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) SetTalkGroups(tg []uint32) {
|
|
|
|
t.accept = map[uint32]bool{t.ID: true}
|
|
|
|
if tg != nil {
|
|
|
|
for _, id := range tg {
|
|
|
|
t.accept[id] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) SetVoiceFrameFunc(f VoiceFrameFunc) {
|
|
|
|
t.vff = f
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) Send(p *dmr.Packet) error {
|
|
|
|
return t.Repeater.Send(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) fmt(p *dmr.Packet, f string) string {
|
|
|
|
var fp = []string{
|
|
|
|
fmt.Sprintf("[slot %d][%02x][",
|
|
|
|
p.Timeslot+1,
|
|
|
|
p.Sequence),
|
|
|
|
}
|
|
|
|
if t.CallMap != nil {
|
|
|
|
if call, ok := t.CallMap[p.SrcID]; ok {
|
|
|
|
fp = append(fp, fmt.Sprintf("%-6s->", call))
|
|
|
|
} else {
|
|
|
|
fp = append(fp, fmt.Sprintf("%-6d->", p.SrcID))
|
|
|
|
}
|
|
|
|
if call, ok := t.CallMap[p.DstID]; ok {
|
|
|
|
fp = append(fp, fmt.Sprintf("%-6s] ", call))
|
|
|
|
} else {
|
|
|
|
fp = append(fp, fmt.Sprintf("%-6d] ", p.DstID))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fp = append(fp, fmt.Sprintf("%-6d->%-6d] ", p.SrcID, p.DstID))
|
|
|
|
}
|
|
|
|
fp = append(fp, f)
|
|
|
|
return strings.Join(fp, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) debugf(p *dmr.Packet, f string, v ...interface{}) {
|
|
|
|
ff := t.fmt(p, f)
|
|
|
|
if len(v) > 0 {
|
|
|
|
log.Debugf(ff, v...)
|
|
|
|
} else {
|
|
|
|
log.Debug(ff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) infof(p *dmr.Packet, f string, v ...interface{}) {
|
|
|
|
ff := t.fmt(p, f)
|
|
|
|
if len(v) > 0 {
|
|
|
|
log.Infof(ff, v...)
|
|
|
|
} else {
|
|
|
|
log.Info(ff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) warningf(p *dmr.Packet, f string, v ...interface{}) {
|
|
|
|
ff := t.fmt(p, f)
|
|
|
|
if len(v) > 0 {
|
|
|
|
log.Warningf(ff, v...)
|
|
|
|
} else {
|
|
|
|
log.Warning(ff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) errorf(p *dmr.Packet, f string, v ...interface{}) {
|
|
|
|
ff := t.fmt(p, f)
|
|
|
|
if len(v) > 0 {
|
|
|
|
log.Errorf(ff, v...)
|
|
|
|
} else {
|
|
|
|
log.Error(ff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) dataBlock(p *dmr.Packet, db *dmr.DataBlock) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
|
|
|
|
if slot.data.header == nil {
|
|
|
|
return errors.New("terminal: logic error, header is nil?!")
|
|
|
|
}
|
|
|
|
if slot.data.header.ResponseRequested {
|
|
|
|
// Only confirmed data blocks have serial numbers stored in them.
|
|
|
|
if int(db.Serial) < len(slot.data.blocks) {
|
|
|
|
slot.data.blocks[db.Serial] = db
|
|
|
|
} else {
|
|
|
|
t.warningf(p, "data block %d out of bounds (%d >= %d)", db.Serial, db.Serial, len(slot.data.blocks))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
slot.data.blocks[slot.data.blocksReceived] = db
|
|
|
|
}
|
|
|
|
|
|
|
|
slot.data.blocksReceived++
|
|
|
|
t.debugf(p, "data block %d/%d", slot.data.blocksReceived, slot.data.blocksExpected)
|
|
|
|
if slot.data.blocksReceived == slot.data.blocksExpected {
|
|
|
|
return t.dataBlockAssemble(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) dataBlockAssemble(p *dmr.Packet) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
|
|
|
|
var (
|
|
|
|
errorsFound bool
|
|
|
|
selective = make([]bool, len(slot.data.blocks))
|
|
|
|
)
|
|
|
|
for i := 0; i < slot.fullMessageBlocks; i++ {
|
|
|
|
if slot.data.blocks[i] == nil || !slot.data.blocks[i].OK {
|
|
|
|
selective[i] = true
|
|
|
|
errorsFound = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if errorsFound {
|
|
|
|
_, responseOk := slot.data.header.Data.(*dmr.ResponseData)
|
|
|
|
switch {
|
|
|
|
case responseOk:
|
|
|
|
t.debugf(p, "found erroneous blocks, not sending out ACK for response")
|
|
|
|
return nil
|
|
|
|
case slot.selectiveAckRequestsSent > 25:
|
|
|
|
t.warningf(p, "found erroneous blocks, max selective ACK reached")
|
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
//t.sendSelectiveAck()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fragment, err := dmr.CombineDataBlocks(slot.data.blocks)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if fragment.Stored > 0 {
|
|
|
|
// Response with data blocks? That must be a selective ACK
|
|
|
|
if _, ok := slot.data.header.Data.(*dmr.ResponseData); ok {
|
|
|
|
// FIXME(pd0mz): deal with this shit
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.dataBlockComplete(p, fragment); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we are not waiting for an ack, then the data session ended
|
|
|
|
if !slot.data.header.ResponseRequested {
|
|
|
|
return t.dataCallEnd(p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) dataBlockComplete(p *dmr.Packet, f *dmr.DataFragment) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
|
|
|
|
var (
|
|
|
|
data []byte
|
|
|
|
size int
|
|
|
|
ddformat = dmr.DDFormatUTF16
|
|
|
|
)
|
|
|
|
|
|
|
|
switch slot.data.header.ServiceAccessPoint {
|
|
|
|
case dmr.ServiceAccessPointShortData:
|
|
|
|
data = f.Data[2:] // Hytera has a 2 byte pre-padding
|
|
|
|
size = f.Stored - 2 - 4 // Leave out the CRC
|
|
|
|
|
|
|
|
if sdd, ok := slot.data.header.Data.(*dmr.ShortDataDefinedData); ok {
|
|
|
|
ddformat = sdd.DDFormat
|
|
|
|
}
|
|
|
|
t.debugf(p, "bytes %d, format %s (%d)", size, dmr.DDFormatName[ddformat], ddformat)
|
|
|
|
break
|
|
|
|
|
|
|
|
default:
|
|
|
|
t.warningf(p, "service accesspoint not implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
if data == nil || size == 0 {
|
|
|
|
t.warningf(p, "no data in message")
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
message, err := dmr.ParseMessageData(data[:size], ddformat, true)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
t.infof(p, "message %q", message)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) callEnd(p *dmr.Packet) error {
|
|
|
|
switch t.state {
|
|
|
|
case dataCallActive:
|
|
|
|
return t.dataCallEnd(p)
|
|
|
|
case voiceCallActive:
|
|
|
|
return t.voiceCallEnd(p)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) dataCallEnd(p *dmr.Packet) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
|
|
|
|
if t.state != dataCallActive {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
slot.data.packetHeaderValid = false
|
|
|
|
t.state = idle
|
|
|
|
t.debugf(p, "data call ended")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) dataCallStart(p *dmr.Packet) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
|
|
|
|
if slot.dstID != p.DstID || slot.srcID != p.SrcID || slot.dataType != p.DataType {
|
|
|
|
if t.state == dataCallActive {
|
|
|
|
if err := t.dataCallEnd(p); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
slot.data.packetHeaderValid = false
|
|
|
|
slot.call.start = time.Now()
|
|
|
|
slot.call.end = time.Time{}
|
|
|
|
slot.dstID = p.DstID
|
|
|
|
slot.srcID = p.SrcID
|
|
|
|
t.state = dataCallActive
|
|
|
|
t.debugf(p, "data call started")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) voiceCallEnd(p *dmr.Packet) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
|
|
|
|
if t.state != voiceCallActive {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
slot.voice.streamID = 0
|
|
|
|
t.state = idle
|
|
|
|
t.debugf(p, "voice call ended")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) voiceCallStart(p *dmr.Packet) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
|
|
|
|
if slot.dstID != p.DstID || slot.srcID != p.SrcID {
|
|
|
|
if err := t.voiceCallEnd(p); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
slot.voice.streamID = p.StreamID
|
|
|
|
t.state = voiceCallActive
|
|
|
|
|
|
|
|
t.debugf(p, "voice call started")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) handlePacket(r dmr.Repeater, p *dmr.Packet) error {
|
|
|
|
// Ignore packets not addressed to us or any of the talk groups we monitor
|
|
|
|
if false && !t.accept[p.DstID] {
|
|
|
|
//log.Debugf("[%d->%d] (%s, %#04b): ignored, not sent to me", p.SrcID, p.DstID, dmr.DataTypeName[p.DataType], p.DataType)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
t.warningf(p, "handle packet: %s", dmr.DataTypeName[p.DataType])
|
|
|
|
//log.Debug(hex.Dump(p.Data))
|
|
|
|
|
|
|
|
//
|
|
|
|
switch p.DataType {
|
|
|
|
case dmr.CSBK:
|
|
|
|
err = t.handleControlBlock(p)
|
|
|
|
break
|
|
|
|
case dmr.Data:
|
|
|
|
err = t.handleData(p)
|
|
|
|
break
|
|
|
|
case dmr.Rate34Data:
|
|
|
|
err = t.handleRate34Data(p)
|
|
|
|
break
|
|
|
|
case dmr.VoiceBurstA, dmr.VoiceBurstB, dmr.VoiceBurstC, dmr.VoiceBurstD, dmr.VoiceBurstE, dmr.VoiceBurstF:
|
|
|
|
err = t.handleVoice(p)
|
|
|
|
break
|
|
|
|
case dmr.VoiceLC:
|
|
|
|
err = t.handleVoiceLC(p)
|
|
|
|
break
|
|
|
|
case dmr.TerminatorWithLC:
|
|
|
|
err = t.handleTerminatorWithLC(p)
|
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
t.warningf(p, "unhandled packet: %s", dmr.DataTypeName[p.DataType])
|
|
|
|
log.Debug(hex.Dump(p.Data))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.errorf(p, "handle packet error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) handleControlBlock(p *dmr.Packet) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
slot.last.packetReceived = time.Now()
|
|
|
|
|
|
|
|
// This ends both data and voice calls
|
|
|
|
if err := t.callEnd(p); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
bits = p.InfoBits()
|
|
|
|
data = make([]byte, 12)
|
|
|
|
)
|
|
|
|
|
|
|
|
if err := bptc.Decode(bits, data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cb, err := dmr.ParseControlBlock(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
t.debugf(p, cb.String())
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) handleData(p *dmr.Packet) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
slot.last.packetReceived = time.Now()
|
|
|
|
|
|
|
|
// This ends voice calls (if any)
|
|
|
|
if err := t.voiceCallEnd(p); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
bits = p.InfoBits()
|
|
|
|
data = make([]byte, 12)
|
|
|
|
)
|
|
|
|
|
|
|
|
if err := bptc.Decode(bits, data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
h, err := dmr.ParseDataHeader(data, false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
slot.data.packetHeaderValid = false
|
|
|
|
slot.data.blocksReceived = 0
|
|
|
|
slot.selectiveAckRequestsSent = 0
|
|
|
|
slot.rxSequence = 0
|
|
|
|
|
|
|
|
t.debugf(p, h.String())
|
|
|
|
switch d := h.Data.(type) {
|
|
|
|
case *dmr.ShortDataDefinedData:
|
|
|
|
if d.FullMessage {
|
|
|
|
slot.fullMessageBlocks = int(d.AppendedBlocks)
|
|
|
|
slot.data.blocks = make([]*dmr.DataBlock, slot.fullMessageBlocks)
|
|
|
|
t.debugf(p, "expecting %d data block", slot.fullMessageBlocks)
|
|
|
|
}
|
|
|
|
slot.data.blocksExpected = int(d.AppendedBlocks)
|
|
|
|
err = t.dataCallStart(p)
|
|
|
|
break
|
|
|
|
|
|
|
|
default:
|
|
|
|
t.warningf(p, "unhandled data header %T", h.Data)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
slot.data.header = h
|
|
|
|
slot.data.packetHeaderValid = true
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) handleRate34Data(p *dmr.Packet) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
slot.last.packetReceived = time.Now()
|
|
|
|
|
|
|
|
if t.state != dataCallActive {
|
|
|
|
t.debugf(p, "no data call in process, ignoring rate ¾ data")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if slot.data.header == nil {
|
|
|
|
t.warningf(p, "got rate ¾ data, but no data header stored")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
bits = p.InfoBits()
|
|
|
|
data = make([]byte, 18)
|
|
|
|
)
|
|
|
|
|
|
|
|
if err := trellis.Decode(bits, data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
db, err := dmr.ParseDataBlock(data, dmr.Rate34Data, slot.data.header.ResponseRequested)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return t.dataBlock(p, db)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) handleTerminatorWithLC(p *dmr.Packet) error {
|
|
|
|
// This ends both data and voice calls
|
|
|
|
if err := t.callEnd(p); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
bits = p.InfoBits()
|
|
|
|
data = make([]byte, 12)
|
|
|
|
)
|
|
|
|
if err := bptc.Decode(bits, data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Applying CRC mask to the checksum. See DMR AI. spec. page 143.
|
|
|
|
data[9] ^= 0x99
|
|
|
|
data[10] ^= 0x99
|
|
|
|
data[11] ^= 0x99
|
|
|
|
|
|
|
|
lc, err := lc.ParseFullLC(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
t.debugf(p, "terminator with lc: %s", lc.String())
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) handleVoice(p *dmr.Packet) error {
|
|
|
|
slot := t.slot[p.Timeslot]
|
|
|
|
slot.last.packetReceived = time.Now()
|
|
|
|
|
|
|
|
switch t.state {
|
|
|
|
case voiceCallActive:
|
|
|
|
if p.StreamID != slot.voice.streamID {
|
|
|
|
// Only accept voice frames from the same stream
|
|
|
|
t.debugf(p, "ignored frame, active stream id: %#08x, this stream id: %#08x", slot.voice.streamID, p.StreamID)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.voiceCallStart(p)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check sync frame
|
|
|
|
sync := p.SyncBits()
|
|
|
|
patt := dmr.SyncPattern(sync)
|
|
|
|
if patt != dmr.SyncPatternUnknown {
|
|
|
|
t.debugf(p, "sync pattern %s", dmr.SyncPatternName[patt])
|
|
|
|
} else {
|
|
|
|
// Not a sync frame, sync field should contain EMB
|
|
|
|
bits, err := dmr.ParseEMBBitsFromSync(sync)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
emb, err := dmr.ParseEMB(bits)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.debugf(p, "embedded signalling %s", emb.String())
|
|
|
|
|
|
|
|
// Handling embedded signalling LC
|
|
|
|
switch emb.LCSS {
|
|
|
|
case dmr.SingleFragment:
|
|
|
|
return nil // FIXME(pd0mz): unhandled
|
|
|
|
case dmr.FirstFragment:
|
|
|
|
slot.embeddedSignalling.Clear()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if emb.LCSS == dmr.FirstFragment || emb.LCSS == dmr.Continuation || emb.LCSS == dmr.LastFragment {
|
|
|
|
frag, err := dmr.ParseEmbeddedSignallingLCFromSyncBits(sync)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := slot.embeddedSignalling.AddBurst(frag); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if emb.LCSS == dmr.LastFragment {
|
|
|
|
if err := slot.embeddedSignalling.CheckAndRepair(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var signalling = make([]byte, 77)
|
|
|
|
if err := slot.embeddedSignalling.GetData(signalling); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
eslc, err := dmr.DeinterleaveEmbeddedSignallingLC(signalling)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !eslc.Check() {
|
|
|
|
return errors.New("embedded signalling LC checksum failed")
|
|
|
|
}
|
|
|
|
|
|
|
|
lc, err := lc.ParseLC(dmr.BitsToBytes(eslc.Bits))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t.debugf(p, "voice embedded lc: %s", lc.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.vff != nil {
|
|
|
|
t.vff(p, p.VoiceBits())
|
|
|
|
if t.SoftwareDelay {
|
|
|
|
delta := time.Now().Sub(slot.last.packetReceived)
|
|
|
|
if delta < VoiceFrameDuration {
|
|
|
|
delay := VoiceFrameDuration - delta
|
|
|
|
time.Sleep(delay)
|
|
|
|
t.debugf(p, "software delay of %s", delay)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terminal) handleVoiceLC(p *dmr.Packet) error {
|
|
|
|
var (
|
|
|
|
bits = p.InfoBits()
|
|
|
|
data = make([]byte, 12)
|
|
|
|
)
|
|
|
|
if err := bptc.Decode(bits, data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Applying CRC mask to the checksum. See DMR AI. spec. page 143.
|
|
|
|
data[9] ^= 0x96
|
|
|
|
data[10] ^= 0x96
|
|
|
|
data[11] ^= 0x96
|
|
|
|
|
|
|
|
lc, err := lc.ParseFullLC(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
t.debugf(p, "voice header lc: %s", lc.String())
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|