Checkpoint
parent
caa595110d
commit
cca6817956
@ -0,0 +1,206 @@
|
|||||||
|
package dmr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Control Block Options
|
||||||
|
const (
|
||||||
|
CBSKOOutboundActivation = B00111000
|
||||||
|
CBSKOUnitToUnitVoiceServiceRequest = B00000100
|
||||||
|
CBSKOUnitToUnitVoiceServiceAnswerResponse = B00000101
|
||||||
|
CBSKONegativeAcknowledgeResponse = B00100100
|
||||||
|
CBSKOPreamble = B00111101
|
||||||
|
)
|
||||||
|
|
||||||
|
type ControlBlock struct {
|
||||||
|
Last bool
|
||||||
|
CBSKO uint8
|
||||||
|
SrcID, DstID uint32
|
||||||
|
Data ControlBlockData
|
||||||
|
}
|
||||||
|
|
||||||
|
type ControlBlockData interface {
|
||||||
|
Write([]byte) error
|
||||||
|
Parse([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundActivation struct{}
|
||||||
|
|
||||||
|
func (d *OutboundActivation) Parse(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OutboundActivation) Write(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
data[0] |= B00111000
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnitToUnitVoiceServiceRequest struct {
|
||||||
|
Options uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *UnitToUnitVoiceServiceRequest) Parse(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
d.Options = data[2]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *UnitToUnitVoiceServiceRequest) Write(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
data[0] |= B00000100
|
||||||
|
data[2] = d.Options
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ (ControlBlockData) = (*UnitToUnitVoiceServiceRequest)(nil)
|
||||||
|
|
||||||
|
type UnitToUnitVoiceServiceAnswerResponse struct {
|
||||||
|
Options uint8
|
||||||
|
Response uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *UnitToUnitVoiceServiceAnswerResponse) Parse(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
d.Options = data[2]
|
||||||
|
d.Response = data[3]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *UnitToUnitVoiceServiceAnswerResponse) Write(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
data[0] |= B00000101
|
||||||
|
data[2] = d.Options
|
||||||
|
data[3] = d.Response
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ (ControlBlockData) = (*UnitToUnitVoiceServiceAnswerResponse)(nil)
|
||||||
|
|
||||||
|
type NegativeAcknowledgeResponse struct {
|
||||||
|
SourceType bool
|
||||||
|
ServiceType uint8
|
||||||
|
Reason uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NegativeAcknowledgeResponse) Parse(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
d.SourceType = (data[2] & B01000000) > 0
|
||||||
|
d.ServiceType = (data[2] & B00011111)
|
||||||
|
d.Reason = data[3]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NegativeAcknowledgeResponse) Write(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
data[0] |= B00100110
|
||||||
|
data[2] = d.ServiceType
|
||||||
|
if d.SourceType {
|
||||||
|
data[2] |= B01000000
|
||||||
|
}
|
||||||
|
data[3] = d.Reason
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ (ControlBlockData) = (*NegativeAcknowledgeResponse)(nil)
|
||||||
|
|
||||||
|
type ControlBlockPreamble struct {
|
||||||
|
DataFollows bool
|
||||||
|
DstIsGroup bool
|
||||||
|
Blocks uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ControlBlockPreamble) Parse(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
d.DataFollows = (data[2] & B10000000) > 0
|
||||||
|
d.DstIsGroup = (data[2] & B01000000) > 0
|
||||||
|
d.Blocks = data[3]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ControlBlockPreamble) Write(data []byte) error {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
data[0] |= B00100110
|
||||||
|
if d.DataFollows {
|
||||||
|
data[2] |= B10000000
|
||||||
|
}
|
||||||
|
if d.DstIsGroup {
|
||||||
|
data[2] |= B01000000
|
||||||
|
}
|
||||||
|
data[3] = d.Blocks
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ (ControlBlockData) = (*ControlBlockPreamble)(nil)
|
||||||
|
|
||||||
|
func ParseControlBlock(data []byte) (*ControlBlock, error) {
|
||||||
|
if len(data) != InfoSize {
|
||||||
|
return nil, fmt.Errorf("dmr: expected %d info bytes, got %d", InfoSize, len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate CRC16
|
||||||
|
|
||||||
|
// Check packet
|
||||||
|
if data[0]&B01000000 > 0 {
|
||||||
|
return nil, errors.New("dmr: CBSK protect flag is set")
|
||||||
|
}
|
||||||
|
if data[1] != 0 {
|
||||||
|
return nil, errors.New("dmr: CBSK feature set ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
cb := &ControlBlock{
|
||||||
|
Last: (data[0] & B10000000) > 0,
|
||||||
|
CBSKO: (data[0] & B00111111),
|
||||||
|
DstID: uint32(data[4])<<16 | uint32(data[5])<<8 | uint32(data[6]),
|
||||||
|
SrcID: uint32(data[7])<<16 | uint32(data[8])<<8 | uint32(data[9]),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cb.CBSKO {
|
||||||
|
case CBSKOOutboundActivation:
|
||||||
|
cb.Data = &OutboundActivation{}
|
||||||
|
break
|
||||||
|
case CBSKOUnitToUnitVoiceServiceRequest:
|
||||||
|
cb.Data = &UnitToUnitVoiceServiceRequest{}
|
||||||
|
break
|
||||||
|
case CBSKOUnitToUnitVoiceServiceAnswerResponse:
|
||||||
|
cb.Data = &UnitToUnitVoiceServiceAnswerResponse{}
|
||||||
|
break
|
||||||
|
case CBSKONegativeAcknowledgeResponse:
|
||||||
|
cb.Data = &NegativeAcknowledgeResponse{}
|
||||||
|
break
|
||||||
|
case CBSKOPreamble:
|
||||||
|
cb.Data = &ControlBlockPreamble{}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("dmr: unknown CBSKO %#02x (%#06b)", cb.CBSKO, cb.CBSKO)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cb.Data.Parse(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb, nil
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package dmr
|
||||||
|
|
||||||
|
type DataBlock struct {
|
||||||
|
Serial uint8
|
||||||
|
CRC uint16
|
||||||
|
OK bool
|
||||||
|
Data [24]byte
|
||||||
|
Length uint8
|
||||||
|
}
|
@ -0,0 +1,214 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
"github.com/pd0mz/go-dmr"
|
||||||
|
"github.com/pd0mz/go-dmr/bptc"
|
||||||
|
"github.com/pd0mz/go-dmr/trellis"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("dmr/terminal")
|
||||||
|
|
||||||
|
const (
|
||||||
|
idle uint8 = iota
|
||||||
|
dataCallActive
|
||||||
|
voideCallActive
|
||||||
|
)
|
||||||
|
|
||||||
|
type Slot struct {
|
||||||
|
call struct {
|
||||||
|
start time.Time
|
||||||
|
end time.Time
|
||||||
|
}
|
||||||
|
dstID, srcID uint32
|
||||||
|
dataType uint8
|
||||||
|
data struct {
|
||||||
|
packetHeaderValid bool
|
||||||
|
blocks [64]dmr.DataBlock
|
||||||
|
blocksExpected uint8
|
||||||
|
blocksReceived uint8
|
||||||
|
}
|
||||||
|
selectiveAckRequestsSent int
|
||||||
|
rxSequence int
|
||||||
|
fullMessageBlocks uint8
|
||||||
|
last struct {
|
||||||
|
packetReceived time.Time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSlot() Slot {
|
||||||
|
return Slot{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Terminal struct {
|
||||||
|
ID uint32
|
||||||
|
Call string
|
||||||
|
Repeater dmr.Repeater
|
||||||
|
slot []Slot
|
||||||
|
state uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(id uint32, call string, r dmr.Repeater) *Terminal {
|
||||||
|
t := &Terminal{
|
||||||
|
ID: id,
|
||||||
|
Call: call,
|
||||||
|
Repeater: r,
|
||||||
|
slot: []Slot{NewSlot(), NewSlot()},
|
||||||
|
}
|
||||||
|
r.SetPacketFunc(t.handlePacket)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) dataCallEnd(p *dmr.Packet) error {
|
||||||
|
slot := t.slot[p.Timeslot]
|
||||||
|
|
||||||
|
if t.state != dataCallActive {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("[%d->%d] data call ended", slot.srcID, slot.dstID)
|
||||||
|
|
||||||
|
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 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
|
||||||
|
|
||||||
|
log.Debugf("[%d->%d] data call started", slot.srcID, slot.dstID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) handlePacket(r dmr.Repeater, p *dmr.Packet) error {
|
||||||
|
var err error
|
||||||
|
if p.DstID != t.ID {
|
||||||
|
//log.Debugf("[%d->%d] (%s, %#04b): ignored, not sent to me", p.SrcID, p.DstID, dmr.DataTypeName[p.DataType], p.DataType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.DataType {
|
||||||
|
case dmr.VoiceBurstA, dmr.VoiceBurstB, dmr.VoiceBurstC, dmr.VoiceBurstD, dmr.VoiceBurstE, dmr.VoiceBurstF:
|
||||||
|
return nil
|
||||||
|
case dmr.CBSK:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("[%d->%d] (%s, %#04b): sent to me", p.SrcID, p.DstID, dmr.DataTypeName[p.DataType], p.DataType)
|
||||||
|
|
||||||
|
switch p.DataType {
|
||||||
|
case dmr.CBSK:
|
||||||
|
err = t.handleControlBlock(p)
|
||||||
|
break
|
||||||
|
case dmr.Data:
|
||||||
|
err = t.handleData(p)
|
||||||
|
break
|
||||||
|
case dmr.Rate34Data:
|
||||||
|
err = t.handleRate34Data(p)
|
||||||
|
default:
|
||||||
|
log.Debug(hex.Dump(p.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("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()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("[%d->%d] control block %T", cb.SrcID, cb.DstID, cb.Data)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) handleData(p *dmr.Packet) error {
|
||||||
|
slot := t.slot[p.Timeslot]
|
||||||
|
slot.last.packetReceived = time.Now()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
c := h.CommonHeader()
|
||||||
|
log.Debugf("[%d->%d] data header %T", c.SrcID, c.DstID, h)
|
||||||
|
|
||||||
|
switch ht := h.(type) {
|
||||||
|
case dmr.ShortDataDefinedDataHeader:
|
||||||
|
if ht.FullMessage {
|
||||||
|
slot.data.blocks = [64]dmr.DataBlock{}
|
||||||
|
slot.fullMessageBlocks = ht.AppendedBlocks
|
||||||
|
log.Debugf("[%d->%d] expecting %d data blocks", c.SrcID, c.DstID, slot.fullMessageBlocks)
|
||||||
|
}
|
||||||
|
slot.data.blocksExpected = ht.AppendedBlocks
|
||||||
|
return t.dataCallStart(p)
|
||||||
|
default:
|
||||||
|
log.Warningf("unhandled data header %T", h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) handleRate34Data(p *dmr.Packet) error {
|
||||||
|
slot := t.slot[p.Timeslot]
|
||||||
|
slot.last.packetReceived = time.Now()
|
||||||
|
|
||||||
|
var (
|
||||||
|
bits = p.InfoBits()
|
||||||
|
data = make([]byte, 18)
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := trellis.Decode(bits, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Print(hex.Dump(data))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue