Checkpoint
This commit is contained in:
parent
caa595110d
commit
cca6817956
13 changed files with 722 additions and 167 deletions
3
bits.go
3
bits.go
|
@ -1,10 +1,11 @@
|
|||
package dmr
|
||||
|
||||
// Various sizes of information chunks.
|
||||
const (
|
||||
PayloadBits = 98 + 10 + 48 + 10 + 98
|
||||
PayloadSize = 33
|
||||
InfoHalfBits = 98
|
||||
InfoBits = 2 * InfoHalfBits
|
||||
InfoSize = 12 // After BPTC(196, 96) decoding
|
||||
SlotTypeHalfBits = 10
|
||||
SlotTypeBits = 2 * SlotTypeHalfBits
|
||||
SignalBits = 48
|
||||
|
|
|
@ -3,11 +3,11 @@ package bptc
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tehmaze/go-dmr"
|
||||
"github.com/tehmaze/go-dmr/fec"
|
||||
"github.com/pd0mz/go-dmr"
|
||||
"github.com/pd0mz/go-dmr/fec"
|
||||
)
|
||||
|
||||
func Process(info []byte, payload []byte) error {
|
||||
func Decode(info []byte, payload []byte) error {
|
||||
if len(info) < 196 {
|
||||
return fmt.Errorf("bptc: info size %d too small, need at least 196 bits", len(info))
|
||||
}
|
||||
|
|
206
controlblock.go
Normal file
206
controlblock.go
Normal file
|
@ -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
|
||||
}
|
9
data.go
Normal file
9
data.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package dmr
|
||||
|
||||
type DataBlock struct {
|
||||
Serial uint8
|
||||
CRC uint16
|
||||
OK bool
|
||||
Data [24]byte
|
||||
Length uint8
|
||||
}
|
|
@ -2,25 +2,6 @@ package dmr
|
|||
|
||||
import "fmt"
|
||||
|
||||
var DataTypeName = [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
|
||||
}
|
||||
|
||||
// Data Header Packet Format
|
||||
const (
|
||||
PacketFormatUDT uint8 = iota // 0b0000
|
||||
|
|
|
@ -9,20 +9,35 @@ import (
|
|||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tehmaze/go-dmr"
|
||||
"github.com/op/go-logging"
|
||||
"github.com/pd0mz/go-dmr"
|
||||
)
|
||||
|
||||
var logger *log.Logger
|
||||
var log = logging.MustGetLogger("dmr/homebrew")
|
||||
|
||||
type AuthStatus uint8
|
||||
|
||||
func (a *AuthStatus) String() string {
|
||||
switch *a {
|
||||
case AuthNone:
|
||||
return "none"
|
||||
case AuthBegin:
|
||||
return "begin"
|
||||
case AuthDone:
|
||||
return "none"
|
||||
case AuthFailed:
|
||||
return "failed"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
AuthNone AuthStatus = iota
|
||||
AuthBegin
|
||||
|
@ -45,10 +60,9 @@ var (
|
|||
|
||||
// We ping the peers every minute
|
||||
var (
|
||||
AuthTimeout = time.Second * 90
|
||||
PingInterval = time.Minute
|
||||
PingTimeout = time.Second * 150
|
||||
RepeaterConfigurationInterval = time.Minute * 5
|
||||
AuthTimeout = time.Second * 5
|
||||
PingInterval = time.Second * 5
|
||||
PingTimeout = time.Second * 15
|
||||
)
|
||||
|
||||
// Peer is a remote repeater that also speaks the Homebrew protocol
|
||||
|
@ -63,12 +77,19 @@ type Peer struct {
|
|||
UnlinkOnAuthFailure bool
|
||||
PacketReceived dmr.PacketFunc
|
||||
Last struct {
|
||||
PacketSent time.Time
|
||||
PacketReceived time.Time
|
||||
PingSent time.Time
|
||||
PongReceived time.Time
|
||||
RepeaterConfigurationSent time.Time
|
||||
PacketSent time.Time
|
||||
PacketReceived time.Time
|
||||
PingSent time.Time
|
||||
PingReceived time.Time
|
||||
PongReceived time.Time
|
||||
}
|
||||
|
||||
// Packed repeater ID
|
||||
id []byte
|
||||
}
|
||||
|
||||
func (p *Peer) CheckRepeaterID(id []byte) bool {
|
||||
return id != nil && p.id != nil && bytes.Equal(id, p.id)
|
||||
}
|
||||
|
||||
func (p *Peer) UpdateToken(nonce []byte) {
|
||||
|
@ -81,11 +102,11 @@ func (p *Peer) UpdateToken(nonce []byte) {
|
|||
|
||||
// Homebrew is implements the Homebrew IPSC DMR Air Interface protocol
|
||||
type Homebrew struct {
|
||||
Config *RepeaterConfiguration
|
||||
Peer map[string]*Peer
|
||||
PeerID map[uint32]*Peer
|
||||
PacketReceived dmr.PacketFunc
|
||||
Config *RepeaterConfiguration
|
||||
Peer map[string]*Peer
|
||||
PeerID map[uint32]*Peer
|
||||
|
||||
pf dmr.PacketFunc
|
||||
conn *net.UDPConn
|
||||
closed bool
|
||||
id []byte
|
||||
|
@ -100,12 +121,15 @@ func New(config *RepeaterConfiguration, addr *net.UDPAddr) (*Homebrew, error) {
|
|||
if config == nil {
|
||||
return nil, errors.New("homebrew: RepeaterConfiguration can't be nil")
|
||||
}
|
||||
if addr == nil {
|
||||
return nil, errors.New("homebrew: addr can't be nil")
|
||||
}
|
||||
|
||||
h := &Homebrew{
|
||||
Config: config,
|
||||
Peer: make(map[string]*Peer),
|
||||
PeerID: make(map[uint32]*Peer),
|
||||
id: []byte(fmt.Sprintf("%08x", config.ID)),
|
||||
id: packRepeaterID(config.ID),
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
if h.conn, err = net.ListenUDP("udp", addr); err != nil {
|
||||
|
@ -128,6 +152,18 @@ func (h *Homebrew) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
log.Info("closing")
|
||||
|
||||
// Tell peers we're closing
|
||||
closing:
|
||||
for _, peer := range h.Peer {
|
||||
if peer.Status == AuthDone {
|
||||
if err := h.WriteToPeer(append(RepeaterClosing, h.id...), peer); err != nil {
|
||||
break closing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kill keepalive goroutine
|
||||
if h.stop != nil {
|
||||
close(h.stop)
|
||||
|
@ -141,6 +177,9 @@ func (h *Homebrew) Close() error {
|
|||
|
||||
// Link establishes a new link with a peer
|
||||
func (h *Homebrew) Link(peer *Peer) error {
|
||||
if peer == nil {
|
||||
return errors.New("homebrew: peer can't be nil")
|
||||
}
|
||||
if peer.Addr == nil {
|
||||
return errors.New("homebrew: peer Addr can't be nil")
|
||||
}
|
||||
|
@ -158,6 +197,7 @@ func (h *Homebrew) Link(peer *Peer) error {
|
|||
peer.Last.PongReceived = time.Time{}
|
||||
|
||||
// Register our peer
|
||||
peer.id = packRepeaterID(peer.ID)
|
||||
h.Peer[peer.Addr.String()] = peer
|
||||
h.PeerID[peer.ID] = peer
|
||||
|
||||
|
@ -191,13 +231,25 @@ func (h *Homebrew) ListenAndServe() error {
|
|||
return err
|
||||
}
|
||||
if err := h.handle(peer, data[:n]); err != nil {
|
||||
if h.closed && strings.HasSuffix(err.Error(), "use of closed network connection") {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("listener closed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Homebrew) GetPacketFunc() dmr.PacketFunc {
|
||||
return h.pf
|
||||
}
|
||||
|
||||
func (h *Homebrew) SetPacketFunc(f dmr.PacketFunc) {
|
||||
h.pf = f
|
||||
}
|
||||
|
||||
func (h *Homebrew) WritePacketToPeer(p *dmr.Packet, peer *Peer) error {
|
||||
return h.WriteToPeer(h.parsePacket(p), peer)
|
||||
}
|
||||
|
@ -216,6 +268,10 @@ func (h *Homebrew) WriteToPeerWithID(b []byte, id uint32) error {
|
|||
return h.WriteToPeer(b, h.getPeer(id))
|
||||
}
|
||||
|
||||
func (h *Homebrew) checkRepeaterID(id []byte) bool {
|
||||
return id != nil && bytes.Equal(id, h.id)
|
||||
}
|
||||
|
||||
func (h *Homebrew) getPeer(id uint32) *Peer {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
@ -253,7 +309,7 @@ func (h *Homebrew) getPeers() []*Peer {
|
|||
func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
|
||||
peer := h.getPeerByAddr(remote)
|
||||
if peer == nil {
|
||||
logger.Printf("ignored packet from unknown peer %s\n", remote)
|
||||
log.Debugf("ignored packet from unknown peer %s\n", remote)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -269,29 +325,19 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
|
|||
}
|
||||
|
||||
if peer.Incoming {
|
||||
// Verify we have a matching peer ID
|
||||
id, err := h.parseRepeaterID(data[4:])
|
||||
if err != nil {
|
||||
logger.Printf("peer %d@%s sent invalid repeater ID (ignored)\n", peer.ID, remote)
|
||||
return nil
|
||||
}
|
||||
var ok = id == peer.ID
|
||||
if !ok {
|
||||
logger.Printf("peer %d@%s sent unexpected repeater ID %d\n", peer.ID, remote, id)
|
||||
}
|
||||
|
||||
switch peer.Status {
|
||||
case AuthNone:
|
||||
switch {
|
||||
case bytes.Equal(data[:4], RepeaterLogin):
|
||||
if !ok {
|
||||
if !peer.CheckRepeaterID(data[4:]) {
|
||||
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[4:]))
|
||||
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
|
||||
}
|
||||
|
||||
// Peer is verified, generate a nonce
|
||||
nonce := make([]byte, 4)
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
logger.Printf("peer %d@%s nonce generation failed: %v\n", peer.ID, remote, err)
|
||||
log.Errorf("peer %d@%s nonce generation failed: %v\n", peer.ID, remote, err)
|
||||
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
|
||||
}
|
||||
|
||||
|
@ -311,34 +357,30 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
|
|||
case AuthBegin:
|
||||
switch {
|
||||
case bytes.Equal(data[:4], RepeaterKey):
|
||||
if ok && len(data) != 76 {
|
||||
logger.Printf("peer %d@%s sent invalid key challenge length of %d\n", peer.ID, remote, len(data))
|
||||
ok = false
|
||||
if !peer.CheckRepeaterID(data[4:]) {
|
||||
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[4:]))
|
||||
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
|
||||
}
|
||||
if !ok {
|
||||
if len(data) != 76 {
|
||||
peer.Status = AuthNone
|
||||
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
|
||||
}
|
||||
|
||||
if !bytes.Equal(data[12:], peer.Token) {
|
||||
logger.Printf("peer %d@%s sent invalid key challenge token\n", peer.ID, remote)
|
||||
log.Errorf("peer %d@%s sent invalid key challenge token\n", peer.ID, remote)
|
||||
peer.Status = AuthNone
|
||||
return h.WriteToPeer(append(MasterNAK, h.id...), peer)
|
||||
}
|
||||
|
||||
peer.Last.PingSent = time.Now()
|
||||
peer.Last.PongReceived = time.Now()
|
||||
peer.Status = AuthDone
|
||||
return h.WriteToPeer(append(MasterACK, h.id...), peer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Verify we have a matching peer ID
|
||||
id, err := h.parseRepeaterID(data[6:14])
|
||||
if err != nil {
|
||||
logger.Printf("peer %d@%s sent invalid repeater ID (ignored)\n", peer.ID, remote)
|
||||
return nil
|
||||
}
|
||||
if id != peer.ID {
|
||||
logger.Printf("peer %d@%s sent unexpected repeater ID %d (ignored)\n", peer.ID, remote, id)
|
||||
if !h.checkRepeaterID(data[6:14]) {
|
||||
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -346,13 +388,13 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
|
|||
case AuthNone:
|
||||
switch {
|
||||
case bytes.Equal(data[:6], MasterACK):
|
||||
logger.Printf("peer %d@%s sent nonce\n", peer.ID, remote)
|
||||
log.Debugf("peer %d@%s sent nonce\n", peer.ID, remote)
|
||||
peer.Status = AuthBegin
|
||||
peer.UpdateToken(data[14:])
|
||||
return h.handleAuth(peer)
|
||||
|
||||
case bytes.Equal(data[:6], MasterNAK):
|
||||
logger.Printf("peer %d@%s refused login\n", peer.ID, remote)
|
||||
log.Errorf("peer %d@%s refused login\n", peer.ID, remote)
|
||||
peer.Status = AuthFailed
|
||||
if peer.UnlinkOnAuthFailure {
|
||||
h.Unlink(peer.ID)
|
||||
|
@ -360,20 +402,21 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
|
|||
break
|
||||
|
||||
default:
|
||||
logger.Printf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote)
|
||||
log.Warningf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote)
|
||||
break
|
||||
}
|
||||
|
||||
case AuthBegin:
|
||||
switch {
|
||||
case bytes.Equal(data[:6], MasterACK):
|
||||
logger.Printf("peer %d@%s accepted login\n", peer.ID, remote)
|
||||
log.Infof("peer %d@%s accepted login\n", peer.ID, remote)
|
||||
peer.Status = AuthDone
|
||||
peer.Last.RepeaterConfigurationSent = time.Now()
|
||||
peer.Last.PingSent = time.Now()
|
||||
peer.Last.PongReceived = time.Now()
|
||||
return h.WriteToPeer(h.Config.Bytes(), peer)
|
||||
|
||||
case bytes.Equal(data[:6], MasterNAK):
|
||||
logger.Printf("peer %d@%s refused login\n", peer.ID, remote)
|
||||
log.Errorf("peer %d@%s refused login\n", peer.ID, remote)
|
||||
peer.Status = AuthFailed
|
||||
if peer.UnlinkOnAuthFailure {
|
||||
h.Unlink(peer.ID)
|
||||
|
@ -381,37 +424,82 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
|
|||
break
|
||||
|
||||
default:
|
||||
logger.Printf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote)
|
||||
log.Warningf("peer %d@%s sent unexpected login reply (ignored)\n", peer.ID, remote)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Authentication is done
|
||||
switch {
|
||||
case bytes.Equal(data[:4], DMRData):
|
||||
p, err := h.parseData(data[4:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.handlePacket(p, peer)
|
||||
if peer.Incoming {
|
||||
switch {
|
||||
case bytes.Equal(data[:4], DMRData):
|
||||
p, err := h.parseData(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.handlePacket(p, peer)
|
||||
|
||||
case peer.Incoming && len(data) == 15 && bytes.Equal(data[:7], MasterPing):
|
||||
// Verify we have a matching peer ID
|
||||
id, err := h.parseRepeaterID(data[7:])
|
||||
if err != nil {
|
||||
logger.Printf("peer %d@%s sent invalid repeater ID (ignored)\n", peer.ID, remote)
|
||||
return nil
|
||||
}
|
||||
if id != peer.ID {
|
||||
logger.Printf("peer %d@%s sent unexpected repeater ID %d (ignored)\n", peer.ID, remote, id)
|
||||
return nil
|
||||
}
|
||||
return h.WriteToPeer(append(RepeaterPong, h.id...), peer)
|
||||
case bytes.Equal(data[:6], MasterACK):
|
||||
break
|
||||
|
||||
default:
|
||||
logger.Printf("peer %d@%s sent unexpected packet\n", peer.ID, remote)
|
||||
break
|
||||
case len(data) == 15 && bytes.Equal(data[:7], MasterPing):
|
||||
return h.WriteToPeer(append(RepeaterPong, data[7:]...), peer)
|
||||
|
||||
default:
|
||||
log.Warningf("peer %d@%s sent unexpected packet (status=%s):\n", peer.ID, remote, peer.Status.String())
|
||||
log.Debug(hex.Dump(data))
|
||||
break
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case bytes.Equal(data[:4], DMRData):
|
||||
p, err := h.parseData(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.handlePacket(p, peer)
|
||||
|
||||
case bytes.Equal(data[:6], MasterACK):
|
||||
if !h.checkRepeaterID(data[6:]) {
|
||||
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
|
||||
return nil
|
||||
}
|
||||
peer.Last.PingSent = time.Now()
|
||||
return h.WriteToPeer(append(MasterPing, h.id...), peer)
|
||||
|
||||
case bytes.Equal(data[:6], MasterNAK):
|
||||
if !h.checkRepeaterID(data[6:]) {
|
||||
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Errorf("peer %d@%s deauthenticated us; re-authenticating\n", peer.ID, remote)
|
||||
peer.Status = AuthNone
|
||||
return h.handleAuth(peer)
|
||||
|
||||
case len(data) == 15 && bytes.Equal(data[:7], RepeaterPong):
|
||||
if !h.checkRepeaterID(data[7:]) {
|
||||
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
|
||||
return nil
|
||||
}
|
||||
peer.Last.PongReceived = time.Now()
|
||||
break
|
||||
|
||||
case len(data) == 10 && bytes.Equal(data[:6], MasterNAK):
|
||||
if !h.checkRepeaterID(data[6:]) {
|
||||
log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
|
||||
return nil
|
||||
}
|
||||
log.Errorf("peer %d@%s sent NAK; re-establishing link\n", peer.ID, remote)
|
||||
peer.Status = AuthNone
|
||||
return h.handleAuth(peer)
|
||||
|
||||
default:
|
||||
log.Warningf("peer %d@%s sent unexpected packet (status=%s):\n", peer.ID, remote, peer.Status.String())
|
||||
log.Debug(hex.Dump(data))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -437,10 +525,10 @@ func (h *Homebrew) handlePacket(p *dmr.Packet, peer *Peer) error {
|
|||
if peer.PacketReceived != nil {
|
||||
return peer.PacketReceived(h, p)
|
||||
}
|
||||
if h.PacketReceived == nil {
|
||||
if h.pf == nil {
|
||||
return errors.New("homebrew: no PacketReceived func defined to handle DMR packet")
|
||||
}
|
||||
return h.PacketReceived(h, p)
|
||||
return h.pf(h, p)
|
||||
}
|
||||
|
||||
func (h *Homebrew) keepalive(stop <-chan bool) {
|
||||
|
@ -453,43 +541,51 @@ func (h *Homebrew) keepalive(stop <-chan bool) {
|
|||
// Ping protocol only applies to outgoing links, and also the auth retries
|
||||
// are entirely up to the peer.
|
||||
if peer.Incoming {
|
||||
continue
|
||||
}
|
||||
|
||||
switch peer.Status {
|
||||
case AuthNone, AuthBegin:
|
||||
switch {
|
||||
case now.Sub(peer.Last.PacketReceived) > AuthTimeout:
|
||||
logger.Printf("peer %d@%s not responding to login; retrying\n", peer.ID, peer.Addr)
|
||||
if err := h.handleAuth(peer); err != nil {
|
||||
logger.Printf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err)
|
||||
switch peer.Status {
|
||||
case AuthDone:
|
||||
switch {
|
||||
case now.Sub(peer.Last.PingReceived) > PingTimeout:
|
||||
peer.Status = AuthNone
|
||||
log.Errorf("peer %d@%s not requesting to ping; dropping connection", peer.ID, peer.Addr)
|
||||
if err := h.WriteToPeer(append(MasterClosing, h.id...), peer); err != nil {
|
||||
log.Errorf("peer %d@%s close failed: %v\n", peer.ID, peer.Addr, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case AuthDone:
|
||||
switch {
|
||||
case now.Sub(peer.Last.PongReceived) > PingTimeout:
|
||||
peer.Status = AuthNone
|
||||
logger.Printf("peer %d@%s not responding to ping; trying to re-establish connection", peer.ID, peer.Addr)
|
||||
if err := h.handleAuth(peer); err != nil {
|
||||
logger.Printf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err)
|
||||
} else {
|
||||
switch peer.Status {
|
||||
case AuthNone, AuthBegin:
|
||||
switch {
|
||||
case now.Sub(peer.Last.PacketReceived) > AuthTimeout:
|
||||
log.Errorf("peer %d@%s not responding to login; retrying\n", peer.ID, peer.Addr)
|
||||
if err := h.handleAuth(peer); err != nil {
|
||||
log.Errorf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
|
||||
case now.Sub(peer.Last.PingSent) > PingInterval:
|
||||
peer.Last.PingSent = now
|
||||
if err := h.WriteToPeer(append(MasterPing, h.id...), peer); err != nil {
|
||||
logger.Printf("peer %d@%s ping failed: %v\n", peer.ID, peer.Addr, err)
|
||||
}
|
||||
break
|
||||
case AuthDone:
|
||||
switch {
|
||||
case now.Sub(peer.Last.PongReceived) > PingTimeout:
|
||||
peer.Status = AuthNone
|
||||
log.Errorf("peer %d@%s not responding to ping; trying to re-establish connection", peer.ID, peer.Addr)
|
||||
if err := h.WriteToPeer(append(RepeaterClosing, h.id...), peer); err != nil {
|
||||
log.Errorf("peer %d@%s close failed: %v\n", peer.ID, peer.Addr, err)
|
||||
}
|
||||
if err := h.handleAuth(peer); err != nil {
|
||||
log.Errorf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err)
|
||||
}
|
||||
break
|
||||
|
||||
case now.Sub(peer.Last.RepeaterConfigurationSent) > RepeaterConfigurationInterval:
|
||||
peer.Last.RepeaterConfigurationSent = time.Now()
|
||||
if err := h.WriteToPeer(h.Config.Bytes(), peer); err != nil {
|
||||
logger.Printf("peer %d@%s repeater configuration failed: %v\n", peer.ID, peer.Addr, err)
|
||||
case now.Sub(peer.Last.PingSent) > PingInterval:
|
||||
peer.Last.PingSent = now
|
||||
if err := h.WriteToPeer(append(MasterPing, h.id...), peer); err != nil {
|
||||
log.Errorf("peer %d@%s ping failed: %v\n", peer.ID, peer.Addr, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -502,18 +598,11 @@ func (h *Homebrew) keepalive(stop <-chan bool) {
|
|||
|
||||
// parseData converts Homebrew packet format to DMR packet format
|
||||
func (h *Homebrew) parseData(data []byte) (*dmr.Packet, error) {
|
||||
if len(data) != 53 {
|
||||
return nil, fmt.Errorf("homebrew: expected 53 data bytes, got %d", len(data))
|
||||
p, err := ParseData(data)
|
||||
if err == nil {
|
||||
p.RepeaterID = h.Config.ID
|
||||
}
|
||||
|
||||
var p = &dmr.Packet{
|
||||
Sequence: data[4],
|
||||
SrcID: uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]),
|
||||
DstID: uint32(data[8])<<16 | uint32(data[9])<<8 | uint32(data[10]),
|
||||
}
|
||||
p.SetData(data[20:])
|
||||
|
||||
return p, nil
|
||||
return p, err
|
||||
}
|
||||
|
||||
// parsePacket converts DMR packet format to Homebrew packet format suitable for sending on the wire
|
||||
|
@ -563,7 +652,7 @@ func (h *Homebrew) parsePacket(p *dmr.Packet) []byte {
|
|||
}
|
||||
|
||||
func (h *Homebrew) parseRepeaterID(data []byte) (uint32, error) {
|
||||
id, err := strconv.ParseUint(string(data), 10, 32)
|
||||
id, err := strconv.ParseUint(string(data), 16, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -573,11 +662,33 @@ func (h *Homebrew) parseRepeaterID(data []byte) (uint32, error) {
|
|||
// Interface compliance check
|
||||
var _ dmr.Repeater = (*Homebrew)(nil)
|
||||
|
||||
// UpdateLogger replaces the package logger.
|
||||
func UpdateLogger(l *log.Logger) {
|
||||
logger = l
|
||||
func packRepeaterID(id uint32) []byte {
|
||||
return []byte(fmt.Sprintf("%08X", id))
|
||||
}
|
||||
|
||||
func init() {
|
||||
UpdateLogger(log.New(os.Stderr, "dmr/homebrew: ", log.LstdFlags))
|
||||
// ParseData converts Homebrew packet format to DMR packet format.
|
||||
func ParseData(data []byte) (*dmr.Packet, error) {
|
||||
if len(data) != 53 {
|
||||
return nil, fmt.Errorf("homebrew: expected 53 data bytes, got %d", len(data))
|
||||
}
|
||||
|
||||
var p = &dmr.Packet{
|
||||
Sequence: data[4],
|
||||
SrcID: uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]),
|
||||
DstID: uint32(data[8])<<16 | uint32(data[9])<<8 | uint32(data[10]),
|
||||
RepeaterID: uint32(data[11])<<24 | uint32(data[12])<<16 | uint32(data[13])<<8 | uint32(data[14]),
|
||||
Timeslot: (data[15] >> 0) & 0x01,
|
||||
CallType: (data[15] >> 1) & 0x01,
|
||||
StreamID: uint32(data[16])<<24 | uint32(data[17])<<16 | uint32(data[18])<<8 | uint32(data[19]),
|
||||
}
|
||||
p.SetData(data[20:])
|
||||
|
||||
switch (data[15] >> 2) & 0x03 {
|
||||
case 0x00, 0x01: // voice (B-F), voice sync (A)
|
||||
p.DataType = dmr.VoiceBurstA + (data[15] >> 4)
|
||||
case 0x02: // data sync
|
||||
p.DataType = (data[15] >> 4)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package homebrew
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tehmaze/go-dmr"
|
||||
"github.com/pd0mz/go-dmr"
|
||||
)
|
||||
|
||||
// RepeaterConfiguration holds information about the current repeater. It
|
||||
|
@ -22,6 +22,8 @@ type RepeaterConfiguration struct {
|
|||
Location string
|
||||
Description string
|
||||
URL string
|
||||
SoftwareID string
|
||||
PackageID string
|
||||
}
|
||||
|
||||
// Bytes returns the configuration as bytes.
|
||||
|
@ -40,6 +42,12 @@ func (r *RepeaterConfiguration) String() string {
|
|||
if r.TXPower > 99 {
|
||||
r.TXPower = 99
|
||||
}
|
||||
if r.SoftwareID == "" {
|
||||
r.SoftwareID = dmr.SoftwareID
|
||||
}
|
||||
if r.PackageID == "" {
|
||||
r.PackageID = dmr.PackageID
|
||||
}
|
||||
|
||||
var lat = fmt.Sprintf("%-08f", r.Latitude)
|
||||
if len(lat) > 8 {
|
||||
|
@ -63,8 +71,8 @@ func (r *RepeaterConfiguration) String() string {
|
|||
b += fmt.Sprintf("%-20s", r.Location)
|
||||
b += fmt.Sprintf("%-20s", r.Description)
|
||||
b += fmt.Sprintf("%-124s", r.URL)
|
||||
b += fmt.Sprintf("%-40s", dmr.SoftwareID)
|
||||
b += fmt.Sprintf("%-40s", dmr.PackageID)
|
||||
b += fmt.Sprintf("%-40s", r.SoftwareID)
|
||||
b += fmt.Sprintf("%-40s", r.PackageID)
|
||||
return b
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Package ipsc implements the Motorola IP Site Connect protocol.
|
||||
package ipsc
|
||||
|
||||
import (
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/tehmaze/go-dmr/dmr"
|
||||
"github.com/pd0mz/go-dmr"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -63,15 +63,15 @@ var (
|
|||
)
|
||||
|
||||
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 []byte // 264 bits
|
||||
Sequence uint8
|
||||
Timeslot uint8 // 0=ts1, 1=ts2
|
||||
FrameType uint8
|
||||
SlotType uint16
|
||||
CallType uint8 // 0=private, 1=group
|
||||
SrcID uint32
|
||||
DstID uint32
|
||||
Payload []byte // 34 bytes
|
||||
Bits []byte // 264 bits
|
||||
Sequence uint8
|
||||
}
|
||||
|
||||
func (p *Packet) Dump() string {
|
||||
|
@ -84,21 +84,21 @@ func (p *Packet) Dump() string {
|
|||
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()
|
||||
s += fmt.Sprintf("payload...: %d bits:\n", len(p.Bits))
|
||||
s += hex.Dump(p.Bits)
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *Packet) InfoBits() []byte {
|
||||
var b = make([]byte, dmr.InfoBits)
|
||||
copy(b[0:dmr.InfoHalfBits], p.PayloadBits[0:dmr.InfoHalfBits])
|
||||
copy(b[dmr.InfoHalfBits:], p.PayloadBits[dmr.InfoHalfBits+dmr.SlotTypeBits+dmr.SignalBits:])
|
||||
copy(b[0:dmr.InfoHalfBits], p.Bits[0:dmr.InfoHalfBits])
|
||||
copy(b[dmr.InfoHalfBits:], p.Bits[dmr.InfoHalfBits+dmr.SlotTypeBits+dmr.SignalBits:])
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *Packet) VoiceBits() []byte {
|
||||
var b = make([]byte, dmr.VoiceBits)
|
||||
copy(b[:dmr.VoiceHalfBits], p.PayloadBits[:dmr.VoiceHalfBits])
|
||||
copy(b[dmr.VoiceHalfBits:], p.PayloadBits[dmr.VoiceHalfBits+dmr.SignalBits:])
|
||||
copy(b[:dmr.VoiceHalfBits], p.Bits[:dmr.VoiceHalfBits])
|
||||
copy(b[dmr.VoiceHalfBits:], p.Bits[dmr.VoiceHalfBits+dmr.SignalBits:])
|
||||
return b
|
||||
}
|
||||
|
|
23
packet.go
23
packet.go
|
@ -5,7 +5,7 @@ const (
|
|||
PrivacyIndicator uint8 = iota // Privacy Indicator information in a standalone burst
|
||||
VoiceLC // Indicates the beginning of voice transmission, carries addressing information
|
||||
TerminatorWithLC // Indicates the end of transmission, carries LC information
|
||||
ControlBlock // Carries a control block
|
||||
CBSK // Carries a control block
|
||||
MultiBlockControl // Header for multi-block control
|
||||
MultiBlockControlContinuation // Follow-on blocks for multi-block control
|
||||
Data // Carries addressing and numbering of packet data blocks
|
||||
|
@ -22,6 +22,27 @@ const (
|
|||
UnknownSlotType
|
||||
)
|
||||
|
||||
var DataTypeName = map[uint8]string{
|
||||
PrivacyIndicator: "privacy indicator",
|
||||
VoiceLC: "voice LC",
|
||||
TerminatorWithLC: "terminator with LC",
|
||||
CBSK: "control block",
|
||||
MultiBlockControl: "multi-block control",
|
||||
MultiBlockControlContinuation: "multi-block control follow-on",
|
||||
Data: "data",
|
||||
Rate12Data: "rate 1/2 packet data",
|
||||
Rate34Data: "rate 3/4 packet data",
|
||||
Idle: "idle",
|
||||
VoiceBurstA: "voice (burst A)",
|
||||
VoiceBurstB: "voice (burst B)",
|
||||
VoiceBurstC: "voice (burst C)",
|
||||
VoiceBurstD: "voice (burst D)",
|
||||
VoiceBurstE: "voice (burst E)",
|
||||
VoiceBurstF: "voice (burst F)",
|
||||
IPSCSync: "IPSC sync",
|
||||
UnknownSlotType: "uknown",
|
||||
}
|
||||
|
||||
// Call Type
|
||||
const (
|
||||
CallTypePrivate uint8 = iota
|
||||
|
|
|
@ -5,4 +5,7 @@ type Repeater interface {
|
|||
Active() bool
|
||||
Close() error
|
||||
ListenAndServe() error
|
||||
|
||||
GetPacketFunc() PacketFunc
|
||||
SetPacketFunc(PacketFunc)
|
||||
}
|
||||
|
|
214
terminal/terminal.go
Normal file
214
terminal/terminal.go
Normal file
|
@ -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
|
||||
}
|
|
@ -4,7 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tehmaze/go-dmr/crc/quadres_16_7"
|
||||
"github.com/pd0mz/go-dmr/crc/quadres_16_7"
|
||||
)
|
||||
|
||||
// EMB LCSS fragments
|
||||
|
|
Loading…
Add table
Reference in a new issue