Initial import
parent
6573e38144
commit
07cd122018
@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"pd0mz/dmr/ipsc"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
configFile := flag.String("config", "dmr.yaml", "configuration file")
|
||||
flag.Parse()
|
||||
|
||||
f, err := os.Open(*configFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
d, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
network := &ipsc.Network{}
|
||||
if err := yaml.Unmarshal(d, network); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
repeater, err := ipsc.New(network)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
repeater.Dump = true
|
||||
panic(repeater.Run())
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
radio_id: 2042214
|
||||
auth_key: 7061737377307264
|
||||
master: brandmeister.pd0zry.ampr.org:55000
|
||||
listen: 0.0.0.0:55000
|
@ -0,0 +1,332 @@
|
||||
package ipsc
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Network struct {
|
||||
Disabled bool
|
||||
RadioID uint32
|
||||
AliveTimer time.Duration
|
||||
MaxMissed int
|
||||
IPSCMode string
|
||||
PeerOperDisabled bool
|
||||
TS1LinkDisabled bool
|
||||
TS2LinkDisabled bool
|
||||
CSBKCall bool
|
||||
RepeaterCallMonitoring bool `yaml:"rcm"`
|
||||
ConsoleApplicationDisabled bool
|
||||
XNLCall bool
|
||||
XNLMaster bool
|
||||
DataCall bool
|
||||
VoiceCall bool
|
||||
MasterPeer bool
|
||||
AuthKey string
|
||||
Master string
|
||||
Listen string
|
||||
}
|
||||
|
||||
type IPSC struct {
|
||||
Network *Network
|
||||
Dump bool
|
||||
|
||||
authKey []byte
|
||||
local struct {
|
||||
addr *net.UDPAddr
|
||||
radioID []byte
|
||||
mode byte
|
||||
flags []byte
|
||||
tsFlags []byte
|
||||
}
|
||||
master struct {
|
||||
addr *net.UDPAddr
|
||||
radioID []byte
|
||||
mode byte
|
||||
flags []byte
|
||||
status struct {
|
||||
connected bool
|
||||
peerList bool
|
||||
keepAliveSent int
|
||||
keepAliveMissed int
|
||||
keepAliveOutstanding int
|
||||
keepAliveReceived int
|
||||
keepAliveRXTime int
|
||||
}
|
||||
}
|
||||
conn *net.UDPConn
|
||||
}
|
||||
|
||||
func New(network *Network) (*IPSC, error) {
|
||||
c := &IPSC{
|
||||
Network: network,
|
||||
}
|
||||
c.local.radioID = make([]byte, 4)
|
||||
c.local.flags = make([]byte, 4)
|
||||
c.master.radioID = make([]byte, 4)
|
||||
c.master.flags = make([]byte, 4)
|
||||
|
||||
binary.BigEndian.PutUint32(c.local.radioID, c.Network.RadioID)
|
||||
|
||||
var err error
|
||||
|
||||
if c.Network.AuthKey != "" {
|
||||
if c.authKey, err = hex.DecodeString(c.Network.AuthKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if c.Network.AliveTimer == 0 {
|
||||
c.Network.AliveTimer = time.Second * 5
|
||||
}
|
||||
if !c.Network.PeerOperDisabled {
|
||||
c.local.mode |= FlagPeerOperational
|
||||
}
|
||||
switch c.Network.IPSCMode {
|
||||
case "analog":
|
||||
c.local.mode |= FlagPeerModeAnalog
|
||||
case "", "digital":
|
||||
c.local.mode |= FlagPeerModeDigital
|
||||
case "none":
|
||||
c.local.mode &= 0xff ^ MaskPeerMode
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown IPSCMode %q", c.Network.IPSCMode)
|
||||
}
|
||||
if c.Network.TS1LinkDisabled {
|
||||
c.local.mode |= FlagIPSCTS1Off
|
||||
} else {
|
||||
c.local.mode |= FlagIPSCTS1On
|
||||
}
|
||||
if c.Network.TS2LinkDisabled {
|
||||
c.local.mode |= FlagIPSCTS2Off
|
||||
} else {
|
||||
c.local.mode |= FlagIPSCTS2On
|
||||
}
|
||||
|
||||
if c.Network.CSBKCall {
|
||||
c.local.flags[2] |= FlagCSBKMessage
|
||||
}
|
||||
if c.Network.RepeaterCallMonitoring {
|
||||
c.local.flags[2] |= FlagRepeaterCallMonitoring
|
||||
}
|
||||
if !c.Network.ConsoleApplicationDisabled {
|
||||
c.local.flags[2] |= FlagConsoleApplication
|
||||
}
|
||||
|
||||
if c.Network.XNLCall {
|
||||
c.local.flags[3] |= FlagXNLStatus
|
||||
if c.Network.XNLMaster {
|
||||
c.local.flags[3] |= FlagXNLMaster
|
||||
} else {
|
||||
c.local.flags[3] |= FlagXNLSlave
|
||||
}
|
||||
}
|
||||
if len(c.Network.AuthKey) > 0 {
|
||||
c.local.flags[3] |= FlagPacketAuthenticated
|
||||
}
|
||||
if c.Network.DataCall {
|
||||
c.local.flags[3] |= FlagDataCall
|
||||
}
|
||||
if c.Network.VoiceCall {
|
||||
c.local.flags[3] |= FlagVoiceCall
|
||||
}
|
||||
if c.Network.MasterPeer {
|
||||
c.local.flags[3] |= FlagMasterPeer
|
||||
}
|
||||
|
||||
if c.Network.Listen == "" {
|
||||
c.Network.Listen = ":62030"
|
||||
}
|
||||
if c.local.addr, err = net.ResolveUDPAddr("udp", c.Network.Listen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Network.Master != "" {
|
||||
if c.master.addr, err = net.ResolveUDPAddr("udp", c.Network.Master); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c.local.tsFlags = append([]byte{c.local.mode}, c.local.flags...)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *IPSC) Run() error {
|
||||
var err error
|
||||
if c.conn, err = net.ListenUDP("udp", c.local.addr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go c.peerMaintenance()
|
||||
|
||||
for {
|
||||
var peer *net.UDPAddr
|
||||
var n int
|
||||
var b = make([]byte, 512)
|
||||
if n, peer, err = c.conn.ReadFromUDP(b); err != nil {
|
||||
log.Printf("error reading from %s: %v\n", peer, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if c.Dump {
|
||||
c.dump(peer, b[:n])
|
||||
}
|
||||
if !c.authenticate(b[:n]) {
|
||||
log.Printf("authentication failed, dropping packet from %s\n", peer)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *IPSC) authenticate(data []byte) bool {
|
||||
if c.authKey == nil || len(c.authKey) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
payload := c.payload(data)
|
||||
hash := data[len(data)-10:]
|
||||
mac := hmac.New(sha1.New, c.authKey)
|
||||
mac.Write(payload)
|
||||
return hmac.Equal(hash, mac.Sum(nil))
|
||||
}
|
||||
|
||||
func (c *IPSC) payload(data []byte) []byte {
|
||||
if c.authKey == nil || len(c.authKey) == 0 {
|
||||
return data
|
||||
}
|
||||
return data[:len(data)-10]
|
||||
}
|
||||
|
||||
func (c *IPSC) dump(addr *net.UDPAddr, data []byte) {
|
||||
if len(data) < 7 {
|
||||
fmt.Printf("%d bytes of unreadable data from %s:\n", len(data), addr)
|
||||
fmt.Printf(hex.Dump(data))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%d bytes of data %s:\n", len(data), addr)
|
||||
fmt.Printf(hex.Dump(data))
|
||||
|
||||
packetType := data[0]
|
||||
peerID := binary.BigEndian.Uint32(data[1:5])
|
||||
seq := data[5:6]
|
||||
|
||||
switch packetType {
|
||||
case CallConfirmation:
|
||||
fmt.Println("call confirmation:")
|
||||
case TextMessageAck:
|
||||
fmt.Println("text message acknowledgement:")
|
||||
case CallMonStatus:
|
||||
fmt.Println("call monitor status:")
|
||||
case CallMonRepeat:
|
||||
fmt.Println("call monitor repeater:")
|
||||
case CallMonNACK:
|
||||
fmt.Println("call monitor nack:")
|
||||
case XCMPXNLControl:
|
||||
fmt.Println("XCMP/XNL control message:")
|
||||
case GroupVoice:
|
||||
fmt.Println("group voice:")
|
||||
case PVTVoice:
|
||||
fmt.Println("PVT voice:")
|
||||
case GroupData:
|
||||
fmt.Println("group data:")
|
||||
case PVTData:
|
||||
fmt.Println("PVT data:")
|
||||
case RPTWakeUp:
|
||||
fmt.Println("RPT wake up:")
|
||||
case UnknownCollision:
|
||||
fmt.Println("unknown collision:")
|
||||
case MasterRegistrationRequest:
|
||||
fmt.Println("master registration request:")
|
||||
case MasterRegistrationReply:
|
||||
fmt.Println("master registration reply:")
|
||||
case PeerListRequest:
|
||||
fmt.Println("peer list request:")
|
||||
case PeerListReply:
|
||||
fmt.Println("peer list reply:")
|
||||
case PeerRegistrationRequest:
|
||||
fmt.Println("peer registration request:")
|
||||
case PeerRegistrationReply:
|
||||
fmt.Println("peer registration reply:")
|
||||
case MasterAliveRequest:
|
||||
fmt.Println("master alive request:")
|
||||
case MasterAliveReply:
|
||||
fmt.Println("master alive reply:")
|
||||
case PeerAliveRequest:
|
||||
fmt.Println("peer alive request:")
|
||||
case PeerAliveReply:
|
||||
fmt.Println("peer alive reply:")
|
||||
case DeregistrationRequest:
|
||||
fmt.Println("de-registration request:")
|
||||
case DeregistrationReply:
|
||||
fmt.Println("de-registration reply:")
|
||||
default:
|
||||
fmt.Printf("unknown packet type 0x%02x:\n", packetType)
|
||||
}
|
||||
fmt.Printf("\tpeer id: %d\n", peerID)
|
||||
fmt.Printf("\tsequence: %v\n", seq)
|
||||
}
|
||||
|
||||
func (c *IPSC) hashedPacket(key, data []byte) []byte {
|
||||
if key == nil || len(key) == 0 {
|
||||
return data
|
||||
}
|
||||
|
||||
mac := hmac.New(sha1.New, key)
|
||||
mac.Write(data)
|
||||
|
||||
hash := make([]byte, 20)
|
||||
hex.Encode(hash, mac.Sum(nil))
|
||||
|
||||
return append(data, hash...)
|
||||
}
|
||||
|
||||
func (c *IPSC) peerMaintenance() {
|
||||
for {
|
||||
var p []byte
|
||||
if c.master.status.connected {
|
||||
log.Println("sending keep-alive to master")
|
||||
r := []byte{MasterAliveRequest}
|
||||
r = append(r, c.local.radioID...)
|
||||
r = append(r, c.local.tsFlags...)
|
||||
r = append(r, []byte{LinkTypeIPSC, Version17, LinkTypeIPSC, Version16}...)
|
||||
p = c.hashedPacket(c.authKey, r)
|
||||
|
||||
} else {
|
||||
log.Println("registering with master")
|
||||
r := []byte{MasterRegistrationRequest}
|
||||
r = append(r, c.local.radioID...)
|
||||
r = append(r, c.local.tsFlags...)
|
||||
r = append(r, []byte{LinkTypeIPSC, Version17, LinkTypeIPSC, Version16}...)
|
||||
p = c.hashedPacket(c.authKey, r)
|
||||
}
|
||||
|
||||
if err := c.sendToMaster(p); err != nil {
|
||||
log.Fatalf("error sending registration request to master: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(c.Network.AliveTimer)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *IPSC) sendToMaster(data []byte) error {
|
||||
if c.Dump {
|
||||
c.dump(c.master.addr, data)
|
||||
}
|
||||
for len(data) > 0 {
|
||||
n, err := c.conn.WriteToUDP(data, c.master.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = data[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package ipsc
|
||||
|
||||
type Mask uint8
|
||||
|
||||
// IPSC mask values
|
||||
|
||||
// Linking status
|
||||
/*
|
||||
Byte 1 - BIT FLAGS:
|
||||
|
||||
xx.. .... = Peer Operational (01 only known valid value)
|
||||
..xx .... = Peer MODE: 00 - No Radio, 01 - Analog, 10 - Digital
|
||||
.... xx.. = IPSC Slot 1: 10 on, 01 off
|
||||
.... ..xx = IPSC Slot 2: 10 on, 01 off
|
||||
*/
|
||||
const (
|
||||
FlagPeerOperational = 0x40
|
||||
MaskPeerMode = 0x30
|
||||
FlagPeerModeAnalog = 0x10
|
||||
FlagPeerModeDigital = 0x20
|
||||
MaskIPSCTS1 = 0x0c
|
||||
MaskIPSCTS2 = 0x03
|
||||
FlagIPSCTS1On = 0x08
|
||||
FlagIPSCTS1Off = 0x04
|
||||
FlagIPSCTS2On = 0x02
|
||||
FlagIPSCTS2Off = 0x01
|
||||
)
|
||||
|
||||
// Service flags
|
||||
/*
|
||||
Byte 1 - 0x00 = Unknown
|
||||
Byte 2 - 0x00 = Unknown
|
||||
Byte 3 - BIT FLAGS:
|
||||
|
||||
x... .... = CSBK Message
|
||||
.x.. .... = Repeater Call Monitoring
|
||||
..x. .... = 3rd Party "Console" Application
|
||||
...x xxxx = Unknown - default to 0
|
||||
*/
|
||||
const (
|
||||
FlagCSBKMessage = 0x80
|
||||
FlagRepeaterCallMonitoring = 0x40
|
||||
FlagConsoleApplication = 0x20
|
||||
)
|
||||
|
||||
/*
|
||||
Byte 4 = BIT FLAGS:
|
||||
|
||||
x... .... = XNL Connected (1=true)
|
||||
.x.. .... = XNL Master Device
|
||||
..x. .... = XNL Slave Device
|
||||
...x .... = Set if packets are authenticated
|
||||
.... x... = Set if data calls are supported
|
||||
.... .x.. = Set if voice calls are supported
|
||||
.... ..x. = Unknown - default to 0
|
||||
.... ...x = Set if master
|
||||
*/
|
||||
const (
|
||||
FlagXNLStatus = 0x80
|
||||
FlagXNLMaster = 0x40
|
||||
FlagXNLSlave = 0x20
|
||||
FlagPacketAuthenticated = 0x10
|
||||
FlagDataCall = 0x08
|
||||
FlagVoiceCall = 0x04
|
||||
FlagMasterPeer = 0x01
|
||||
)
|
||||
|
||||
// Timeslot call and status byte
|
||||
/*
|
||||
Byte 17 of Group and Private Voice/Data Packets
|
||||
|
||||
..x.. ....TS Value (0=TS1, 1=TS2)
|
||||
.x... ....TS In Progress/End (0=In Progress, 1=End)
|
||||
|
||||
Possible values: 0x00=TS1, 0x20=TS2, 0x40=TS1 End, 0x60=TS2 End
|
||||
*/
|
||||
|
||||
// RTP mask values
|
||||
/*
|
||||
Bytes 1 and 2 of the RTP header are bit-fields, the rest are at least
|
||||
one byte long, and do not need masked.
|
||||
*/
|
||||
const (
|
||||
// Byte 1
|
||||
RTPVersionMask Mask = 0xc0
|
||||
RTPPadMask Mask = 0x20
|
||||
RTPExtMask Mask = 0x10
|
||||
RTPCSICMask Mask = 0x0f
|
||||
// Byte 2
|
||||
RTPMRKRMask Mask = 0x80
|
||||
RTPPayTypeMask Mask = 0xf7
|
||||
)
|
@ -0,0 +1,74 @@
|
||||
package ipsc
|
||||
|
||||
const (
|
||||
// IPSC Version Information
|
||||
Version14 byte = 0x00
|
||||
Version15 byte = 0x00
|
||||
Version15A byte = 0x00
|
||||
Version16 byte = 0x01
|
||||
Version17 byte = 0x02
|
||||
Version18 byte = 0x02
|
||||
Version19 byte = 0x03
|
||||
Version22 byte = 0x04
|
||||
|
||||
// Known IPSC Message Types
|
||||
CallConfirmation byte = 0x05 // Confirmation FROM the recipient of a confirmed call.
|
||||
TextMessageAck byte = 0x54 // Doesn't seem to mean success, though. This code is sent success or failure
|
||||
CallMonStatus byte = 0x61 // |
|
||||
CallMonRepeat byte = 0x62 // | Exact meaning unknown
|
||||
CallMonNACK byte = 0x63 // |
|
||||
XCMPXNLControl byte = 0x70 // XCMP/XNL control message
|
||||
GroupVoice byte = 0x80
|
||||
PVTVoice byte = 0x81
|
||||
GroupData byte = 0x83
|
||||
PVTData byte = 0x84
|
||||
RPTWakeUp byte = 0x85 // Similar to OTA DMR "wake up"
|
||||
UnknownCollision byte = 0x86 // Seen when two dmrlinks try to transmit at once
|
||||
MasterRegistrationRequest byte = 0x90 // FROM peer TO master
|
||||
MasterRegistrationReply byte = 0x91 // FROM master TO peer
|
||||
PeerListRequest byte = 0x92 // From peer TO master
|
||||
PeerListReply byte = 0x93 // From master TO peer
|
||||
PeerRegistrationRequest byte = 0x94 // Peer registration request
|
||||
PeerRegistrationReply byte = 0x95 // Peer registration reply
|
||||
MasterAliveRequest byte = 0x96 // FROM peer TO master
|
||||
MasterAliveReply byte = 0x97 // FROM master TO peer
|
||||
PeerAliveRequest byte = 0x98 // Peer keep alive request
|
||||
PeerAliveReply byte = 0x99 // Peer keep alive reply
|
||||
DeregistrationRequest byte = 0x9a // Request de-registration from system
|
||||
DeregistrationReply byte = 0x9b // De-registration reply
|
||||
|
||||
// Link Type Values
|
||||
LinkTypeIPSC byte = 0x04
|
||||
)
|
||||
|
||||
var AnyPeerRequired = map[byte]bool{
|
||||
GroupVoice: true,
|
||||
PVTVoice: true,
|
||||
GroupData: true,
|
||||
PVTData: true,
|
||||
CallMonStatus: true,
|
||||
CallMonRepeat: true,
|
||||
CallMonNACK: true,
|
||||
XCMPXNLControl: true,
|
||||
RPTWakeUp: true,
|
||||
DeregistrationRequest: true,
|
||||
}
|
||||
|
||||
var PeerRequired = map[byte]bool{
|
||||
PeerAliveRequest: true,
|
||||
PeerAliveReply: true,
|
||||
PeerRegistrationRequest: true,
|
||||
PeerRegistrationReply: true,
|
||||
}
|
||||
|
||||
var MasterRequired = map[byte]bool{
|
||||
PeerListReply: true,
|
||||
MasterAliveReply: true,
|
||||
}
|
||||
|
||||
var UserGenerated = map[byte]bool{
|
||||
GroupVoice: true,
|
||||
PVTVoice: true,
|
||||
GroupData: true,
|
||||
PVTData: true,
|
||||
}
|
Loading…
Reference in New Issue