Checkpoint

pull/1/head
Wijnand Modderman-Lenstra 9 years ago
parent caa595110d
commit cca6817956

@ -1,10 +1,11 @@
package dmr package dmr
// Various sizes of information chunks.
const ( const (
PayloadBits = 98 + 10 + 48 + 10 + 98 PayloadBits = 98 + 10 + 48 + 10 + 98
PayloadSize = 33
InfoHalfBits = 98 InfoHalfBits = 98
InfoBits = 2 * InfoHalfBits InfoBits = 2 * InfoHalfBits
InfoSize = 12 // After BPTC(196, 96) decoding
SlotTypeHalfBits = 10 SlotTypeHalfBits = 10
SlotTypeBits = 2 * SlotTypeHalfBits SlotTypeBits = 2 * SlotTypeHalfBits
SignalBits = 48 SignalBits = 48

@ -3,11 +3,11 @@ package bptc
import ( import (
"fmt" "fmt"
"github.com/tehmaze/go-dmr" "github.com/pd0mz/go-dmr"
"github.com/tehmaze/go-dmr/fec" "github.com/pd0mz/go-dmr/fec"
) )
func Process(info []byte, payload []byte) error { func Decode(info []byte, payload []byte) error {
if len(info) < 196 { if len(info) < 196 {
return fmt.Errorf("bptc: info size %d too small, need at least 196 bits", len(info)) return fmt.Errorf("bptc: info size %d too small, need at least 196 bits", len(info))
} }

@ -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
}

@ -2,25 +2,6 @@ package dmr
import "fmt" 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 // Data Header Packet Format
const ( const (
PacketFormatUDT uint8 = iota // 0b0000 PacketFormatUDT uint8 = iota // 0b0000

@ -9,20 +9,35 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"log"
"net" "net"
"os"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "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 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 ( const (
AuthNone AuthStatus = iota AuthNone AuthStatus = iota
AuthBegin AuthBegin
@ -45,10 +60,9 @@ var (
// We ping the peers every minute // We ping the peers every minute
var ( var (
AuthTimeout = time.Second * 90 AuthTimeout = time.Second * 5
PingInterval = time.Minute PingInterval = time.Second * 5
PingTimeout = time.Second * 150 PingTimeout = time.Second * 15
RepeaterConfigurationInterval = time.Minute * 5
) )
// Peer is a remote repeater that also speaks the Homebrew protocol // Peer is a remote repeater that also speaks the Homebrew protocol
@ -63,12 +77,19 @@ type Peer struct {
UnlinkOnAuthFailure bool UnlinkOnAuthFailure bool
PacketReceived dmr.PacketFunc PacketReceived dmr.PacketFunc
Last struct { Last struct {
PacketSent time.Time PacketSent time.Time
PacketReceived time.Time PacketReceived time.Time
PingSent time.Time PingSent time.Time
PongReceived time.Time PingReceived time.Time
RepeaterConfigurationSent 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) { 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 // Homebrew is implements the Homebrew IPSC DMR Air Interface protocol
type Homebrew struct { type Homebrew struct {
Config *RepeaterConfiguration Config *RepeaterConfiguration
Peer map[string]*Peer Peer map[string]*Peer
PeerID map[uint32]*Peer PeerID map[uint32]*Peer
PacketReceived dmr.PacketFunc
pf dmr.PacketFunc
conn *net.UDPConn conn *net.UDPConn
closed bool closed bool
id []byte id []byte
@ -100,12 +121,15 @@ func New(config *RepeaterConfiguration, addr *net.UDPAddr) (*Homebrew, error) {
if config == nil { if config == nil {
return nil, errors.New("homebrew: RepeaterConfiguration can't be 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{ h := &Homebrew{
Config: config, Config: config,
Peer: make(map[string]*Peer), Peer: make(map[string]*Peer),
PeerID: make(map[uint32]*Peer), PeerID: make(map[uint32]*Peer),
id: []byte(fmt.Sprintf("%08x", config.ID)), id: packRepeaterID(config.ID),
mutex: &sync.Mutex{}, mutex: &sync.Mutex{},
} }
if h.conn, err = net.ListenUDP("udp", addr); err != nil { if h.conn, err = net.ListenUDP("udp", addr); err != nil {
@ -128,6 +152,18 @@ func (h *Homebrew) Close() error {
return nil 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 // Kill keepalive goroutine
if h.stop != nil { if h.stop != nil {
close(h.stop) close(h.stop)
@ -141,6 +177,9 @@ func (h *Homebrew) Close() error {
// Link establishes a new link with a peer // Link establishes a new link with a peer
func (h *Homebrew) Link(peer *Peer) error { func (h *Homebrew) Link(peer *Peer) error {
if peer == nil {
return errors.New("homebrew: peer can't be nil")
}
if peer.Addr == nil { if peer.Addr == nil {
return errors.New("homebrew: peer Addr can't be 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{} peer.Last.PongReceived = time.Time{}
// Register our peer // Register our peer
peer.id = packRepeaterID(peer.ID)
h.Peer[peer.Addr.String()] = peer h.Peer[peer.Addr.String()] = peer
h.PeerID[peer.ID] = peer h.PeerID[peer.ID] = peer
@ -191,13 +231,25 @@ func (h *Homebrew) ListenAndServe() error {
return err return err
} }
if err := h.handle(peer, data[:n]); err != nil { if err := h.handle(peer, data[:n]); err != nil {
if h.closed && strings.HasSuffix(err.Error(), "use of closed network connection") {
break
}
return err return err
} }
} }
log.Info("listener closed")
return nil 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 { func (h *Homebrew) WritePacketToPeer(p *dmr.Packet, peer *Peer) error {
return h.WriteToPeer(h.parsePacket(p), peer) 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)) 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 { func (h *Homebrew) getPeer(id uint32) *Peer {
h.mutex.Lock() h.mutex.Lock()
defer h.mutex.Unlock() defer h.mutex.Unlock()
@ -253,7 +309,7 @@ func (h *Homebrew) getPeers() []*Peer {
func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error { func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
peer := h.getPeerByAddr(remote) peer := h.getPeerByAddr(remote)
if peer == nil { 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 return nil
} }
@ -269,29 +325,19 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
} }
if peer.Incoming { 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 { switch peer.Status {
case AuthNone: case AuthNone:
switch { switch {
case bytes.Equal(data[:4], RepeaterLogin): 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) return h.WriteToPeer(append(MasterNAK, h.id...), peer)
} }
// Peer is verified, generate a nonce // Peer is verified, generate a nonce
nonce := make([]byte, 4) nonce := make([]byte, 4)
if _, err := rand.Read(nonce); err != nil { 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) return h.WriteToPeer(append(MasterNAK, h.id...), peer)
} }
@ -311,34 +357,30 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
case AuthBegin: case AuthBegin:
switch { switch {
case bytes.Equal(data[:4], RepeaterKey): case bytes.Equal(data[:4], RepeaterKey):
if ok && len(data) != 76 { if !peer.CheckRepeaterID(data[4:]) {
logger.Printf("peer %d@%s sent invalid key challenge length of %d\n", peer.ID, remote, len(data)) log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[4:]))
ok = false return h.WriteToPeer(append(MasterNAK, h.id...), peer)
} }
if !ok { if len(data) != 76 {
peer.Status = AuthNone peer.Status = AuthNone
return h.WriteToPeer(append(MasterNAK, h.id...), peer) return h.WriteToPeer(append(MasterNAK, h.id...), peer)
} }
if !bytes.Equal(data[12:], peer.Token) { 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 peer.Status = AuthNone
return h.WriteToPeer(append(MasterNAK, h.id...), peer) return h.WriteToPeer(append(MasterNAK, h.id...), peer)
} }
peer.Last.PingSent = time.Now()
peer.Last.PongReceived = time.Now()
peer.Status = AuthDone peer.Status = AuthDone
return h.WriteToPeer(append(MasterACK, h.id...), peer) return h.WriteToPeer(append(MasterACK, h.id...), peer)
} }
} }
} else { } else {
// Verify we have a matching peer ID // Verify we have a matching peer ID
id, err := h.parseRepeaterID(data[6:14]) if !h.checkRepeaterID(data[6:14]) {
if err != nil { log.Warningf("peer %d@%s sent invalid repeater ID %q (ignored)\n", peer.ID, remote, string(data[6:14]))
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 nil
} }
@ -346,13 +388,13 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
case AuthNone: case AuthNone:
switch { switch {
case bytes.Equal(data[:6], MasterACK): 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.Status = AuthBegin
peer.UpdateToken(data[14:]) peer.UpdateToken(data[14:])
return h.handleAuth(peer) return h.handleAuth(peer)
case bytes.Equal(data[:6], MasterNAK): 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 peer.Status = AuthFailed
if peer.UnlinkOnAuthFailure { if peer.UnlinkOnAuthFailure {
h.Unlink(peer.ID) h.Unlink(peer.ID)
@ -360,20 +402,21 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
break break
default: 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 break
} }
case AuthBegin: case AuthBegin:
switch { switch {
case bytes.Equal(data[:6], MasterACK): 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.Status = AuthDone
peer.Last.RepeaterConfigurationSent = time.Now() peer.Last.PingSent = time.Now()
peer.Last.PongReceived = time.Now()
return h.WriteToPeer(h.Config.Bytes(), peer) return h.WriteToPeer(h.Config.Bytes(), peer)
case bytes.Equal(data[:6], MasterNAK): 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 peer.Status = AuthFailed
if peer.UnlinkOnAuthFailure { if peer.UnlinkOnAuthFailure {
h.Unlink(peer.ID) h.Unlink(peer.ID)
@ -381,37 +424,82 @@ func (h *Homebrew) handle(remote *net.UDPAddr, data []byte) error {
break break
default: 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 break
} }
} }
} }
} else { } else {
// Authentication is done // Authentication is done
switch { if peer.Incoming {
case bytes.Equal(data[:4], DMRData): switch {
p, err := h.parseData(data[4:]) case bytes.Equal(data[:4], DMRData):
if err != nil { p, err := h.parseData(data)
return err if err != nil {
} return err
return h.handlePacket(p, peer) }
return h.handlePacket(p, peer)
case peer.Incoming && len(data) == 15 && bytes.Equal(data[:7], MasterPing): case bytes.Equal(data[:6], MasterACK):
// Verify we have a matching peer ID break
id, err := h.parseRepeaterID(data[7:])
if err != nil { case len(data) == 15 && bytes.Equal(data[:7], MasterPing):
logger.Printf("peer %d@%s sent invalid repeater ID (ignored)\n", peer.ID, remote) return h.WriteToPeer(append(RepeaterPong, data[7:]...), peer)
return nil
} default:
if id != peer.ID { log.Warningf("peer %d@%s sent unexpected packet (status=%s):\n", peer.ID, remote, peer.Status.String())
logger.Printf("peer %d@%s sent unexpected repeater ID %d (ignored)\n", peer.ID, remote, id) log.Debug(hex.Dump(data))
return nil break
} }
return h.WriteToPeer(append(RepeaterPong, h.id...), peer) } 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
default: case len(data) == 10 && bytes.Equal(data[:6], MasterNAK):
logger.Printf("peer %d@%s sent unexpected packet\n", peer.ID, remote) if !h.checkRepeaterID(data[6:]) {
break 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 { if peer.PacketReceived != nil {
return peer.PacketReceived(h, p) 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 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) { 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 // Ping protocol only applies to outgoing links, and also the auth retries
// are entirely up to the peer. // are entirely up to the peer.
if peer.Incoming { if peer.Incoming {
continue switch peer.Status {
} case AuthDone:
switch {
switch peer.Status { case now.Sub(peer.Last.PingReceived) > PingTimeout:
case AuthNone, AuthBegin: peer.Status = AuthNone
switch { log.Errorf("peer %d@%s not requesting to ping; dropping connection", peer.ID, peer.Addr)
case now.Sub(peer.Last.PacketReceived) > AuthTimeout: if err := h.WriteToPeer(append(MasterClosing, h.id...), peer); err != nil {
logger.Printf("peer %d@%s not responding to login; retrying\n", peer.ID, peer.Addr) log.Errorf("peer %d@%s close failed: %v\n", peer.ID, peer.Addr, err)
if err := h.handleAuth(peer); err != nil { }
logger.Printf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err) break
} }
break break
} }
} else {
case AuthDone: switch peer.Status {
switch { case AuthNone, AuthBegin:
case now.Sub(peer.Last.PongReceived) > PingTimeout: switch {
peer.Status = AuthNone case now.Sub(peer.Last.PacketReceived) > AuthTimeout:
logger.Printf("peer %d@%s not responding to ping; trying to re-establish connection", peer.ID, peer.Addr) log.Errorf("peer %d@%s not responding to login; retrying\n", peer.ID, peer.Addr)
if err := h.handleAuth(peer); err != nil { if err := h.handleAuth(peer); err != nil {
logger.Printf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err) log.Errorf("peer %d@%s retry failed: %v\n", peer.ID, peer.Addr, err)
}
break
} }
break
case now.Sub(peer.Last.PingSent) > PingInterval: case AuthDone:
peer.Last.PingSent = now switch {
if err := h.WriteToPeer(append(MasterPing, h.id...), peer); err != nil { case now.Sub(peer.Last.PongReceived) > PingTimeout:
logger.Printf("peer %d@%s ping failed: %v\n", peer.ID, peer.Addr, err) 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.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
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)
}
break
} }
} }
} }
@ -502,18 +598,11 @@ func (h *Homebrew) keepalive(stop <-chan bool) {
// parseData converts Homebrew packet format to DMR packet format // parseData converts Homebrew packet format to DMR packet format
func (h *Homebrew) parseData(data []byte) (*dmr.Packet, error) { func (h *Homebrew) parseData(data []byte) (*dmr.Packet, error) {
if len(data) != 53 { p, err := ParseData(data)
return nil, fmt.Errorf("homebrew: expected 53 data bytes, got %d", len(data)) if err == nil {
p.RepeaterID = h.Config.ID
} }
return p, err
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
} }
// parsePacket converts DMR packet format to Homebrew packet format suitable for sending on the wire // 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) { 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 { if err != nil {
return 0, err return 0, err
} }
@ -573,11 +662,33 @@ func (h *Homebrew) parseRepeaterID(data []byte) (uint32, error) {
// Interface compliance check // Interface compliance check
var _ dmr.Repeater = (*Homebrew)(nil) var _ dmr.Repeater = (*Homebrew)(nil)
// UpdateLogger replaces the package logger. func packRepeaterID(id uint32) []byte {
func UpdateLogger(l *log.Logger) { return []byte(fmt.Sprintf("%08X", id))
logger = l
} }
func init() { // ParseData converts Homebrew packet format to DMR packet format.
UpdateLogger(log.New(os.Stderr, "dmr/homebrew: ", log.LstdFlags)) 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 ( import (
"fmt" "fmt"
"github.com/tehmaze/go-dmr" "github.com/pd0mz/go-dmr"
) )
// RepeaterConfiguration holds information about the current repeater. It // RepeaterConfiguration holds information about the current repeater. It
@ -22,6 +22,8 @@ type RepeaterConfiguration struct {
Location string Location string
Description string Description string
URL string URL string
SoftwareID string
PackageID string
} }
// Bytes returns the configuration as bytes. // Bytes returns the configuration as bytes.
@ -40,6 +42,12 @@ func (r *RepeaterConfiguration) String() string {
if r.TXPower > 99 { if r.TXPower > 99 {
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) var lat = fmt.Sprintf("%-08f", r.Latitude)
if len(lat) > 8 { if len(lat) > 8 {
@ -63,8 +71,8 @@ func (r *RepeaterConfiguration) String() string {
b += fmt.Sprintf("%-20s", r.Location) b += fmt.Sprintf("%-20s", r.Location)
b += fmt.Sprintf("%-20s", r.Description) b += fmt.Sprintf("%-20s", r.Description)
b += fmt.Sprintf("%-124s", r.URL) b += fmt.Sprintf("%-124s", r.URL)
b += fmt.Sprintf("%-40s", dmr.SoftwareID) b += fmt.Sprintf("%-40s", r.SoftwareID)
b += fmt.Sprintf("%-40s", dmr.PackageID) b += fmt.Sprintf("%-40s", r.PackageID)
return b return b
} }

@ -1,3 +1,4 @@
// Package ipsc implements the Motorola IP Site Connect protocol.
package ipsc package ipsc
import ( import (

@ -4,7 +4,7 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/tehmaze/go-dmr/dmr" "github.com/pd0mz/go-dmr"
) )
const ( const (
@ -63,15 +63,15 @@ var (
) )
type Packet struct { type Packet struct {
Timeslot uint8 // 0=ts1, 1=ts2 Timeslot uint8 // 0=ts1, 1=ts2
FrameType uint8 FrameType uint8
SlotType uint16 SlotType uint16
CallType uint8 // 0=private, 1=group CallType uint8 // 0=private, 1=group
SrcID uint32 SrcID uint32
DstID uint32 DstID uint32
Payload []byte // 34 bytes Payload []byte // 34 bytes
PayloadBits []byte // 264 bits Bits []byte // 264 bits
Sequence uint8 Sequence uint8
} }
func (p *Packet) Dump() string { 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("target....: %d\n", p.DstID)
s += fmt.Sprintf("payload...: %d bytes (swapped):\n", len(p.Payload)) s += fmt.Sprintf("payload...: %d bytes (swapped):\n", len(p.Payload))
s += hex.Dump(p.Payload) s += hex.Dump(p.Payload)
s += fmt.Sprintf("payload...: %d bits:\n", len(p.PayloadBits)) s += fmt.Sprintf("payload...: %d bits:\n", len(p.Bits))
s += p.PayloadBits.Dump() s += hex.Dump(p.Bits)
return s return s
} }
func (p *Packet) InfoBits() []byte { func (p *Packet) InfoBits() []byte {
var b = make([]byte, dmr.InfoBits) var b = make([]byte, dmr.InfoBits)
copy(b[0:dmr.InfoHalfBits], p.PayloadBits[0:dmr.InfoHalfBits]) copy(b[0:dmr.InfoHalfBits], p.Bits[0:dmr.InfoHalfBits])
copy(b[dmr.InfoHalfBits:], p.PayloadBits[dmr.InfoHalfBits+dmr.SlotTypeBits+dmr.SignalBits:]) copy(b[dmr.InfoHalfBits:], p.Bits[dmr.InfoHalfBits+dmr.SlotTypeBits+dmr.SignalBits:])
return b return b
} }
func (p *Packet) VoiceBits() []byte { func (p *Packet) VoiceBits() []byte {
var b = make([]byte, dmr.VoiceBits) var b = make([]byte, dmr.VoiceBits)
copy(b[:dmr.VoiceHalfBits], p.PayloadBits[:dmr.VoiceHalfBits]) copy(b[:dmr.VoiceHalfBits], p.Bits[:dmr.VoiceHalfBits])
copy(b[dmr.VoiceHalfBits:], p.PayloadBits[dmr.VoiceHalfBits+dmr.SignalBits:]) copy(b[dmr.VoiceHalfBits:], p.Bits[dmr.VoiceHalfBits+dmr.SignalBits:])
return b return b
} }

@ -5,7 +5,7 @@ const (
PrivacyIndicator uint8 = iota // Privacy Indicator information in a standalone burst PrivacyIndicator uint8 = iota // Privacy Indicator information in a standalone burst
VoiceLC // Indicates the beginning of voice transmission, carries addressing information VoiceLC // Indicates the beginning of voice transmission, carries addressing information
TerminatorWithLC // Indicates the end of transmission, carries LC 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 MultiBlockControl // Header for multi-block control
MultiBlockControlContinuation // Follow-on blocks for multi-block control MultiBlockControlContinuation // Follow-on blocks for multi-block control
Data // Carries addressing and numbering of packet data blocks Data // Carries addressing and numbering of packet data blocks
@ -22,6 +22,27 @@ const (
UnknownSlotType 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 // Call Type
const ( const (
CallTypePrivate uint8 = iota CallTypePrivate uint8 = iota

@ -5,4 +5,7 @@ type Repeater interface {
Active() bool Active() bool
Close() error Close() error
ListenAndServe() error ListenAndServe() error
GetPacketFunc() PacketFunc
SetPacketFunc(PacketFunc)
} }

@ -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" "errors"
"fmt" "fmt"
"github.com/tehmaze/go-dmr/crc/quadres_16_7" "github.com/pd0mz/go-dmr/crc/quadres_16_7"
) )
// EMB LCSS fragments // EMB LCSS fragments

Loading…
Cancel
Save