Added homebrew protocol

pull/1/head
Wijnand Modderman-Lenstra 9 years ago
parent 07cd122018
commit f5e7f26850

@ -0,0 +1,89 @@
package main
import (
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"os"
"pd0mz/dmr/homebrew"
"gopkg.in/yaml.v2"
)
func rc() *homebrew.RepeaterConfiguration {
return &homebrew.RepeaterConfiguration{
Callsign: "PI1BOL",
RepeaterID: 2043044,
RXFreq: 433787500,
TXFreq: 438787500,
TXPower: 5,
ColorCode: 1,
Latitude: 52.296786,
Longitude: 4.595454,
Height: 12,
Location: "Hillegom, ZH, NL",
Description: fmt.Sprintf("%s go-dmr", homebrew.Version),
URL: "https://github.com/pd0mz",
}
}
var (
callType = map[homebrew.CallType]string{
homebrew.GroupCall: "group",
homebrew.UnitCall: "unit",
}
frameType = map[homebrew.FrameType]string{
homebrew.Voice: "voice",
homebrew.VoiceSync: "voice sync",
homebrew.DataSync: "data sync",
homebrew.UnusedFrameType: "unused (should not happen)",
}
)
func dump(d *homebrew.Data) {
fmt.Println("DMR data:")
fmt.Printf("\tsequence: %d\n", d.Sequence)
fmt.Printf("\ttarget..: %d -> %d\n", d.SrcID, d.DstID)
fmt.Printf("\trepeater: %d\n", d.RepeaterID)
fmt.Printf("\tslot....: %d\n", d.Slot())
fmt.Printf("\tcall....: %s\n", callType[d.CallType()])
fmt.Printf("\tframe...: %s\n", frameType[d.FrameType()])
switch d.FrameType() {
case homebrew.DataSync:
fmt.Printf("\tdatatype: %d\n", d.DataType())
case homebrew.Voice, homebrew.VoiceSync:
fmt.Printf("\tsequence: %c (voice)\n", 'A'+d.DataType())
}
fmt.Printf("\tdump....:\n")
fmt.Println(hex.Dump(d.DMR[:]))
}
func main() {
configFile := flag.String("config", "dmr-homebrew.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 := &homebrew.Network{}
if err := yaml.Unmarshal(d, network); err != nil {
panic(err)
}
repeater, err := homebrew.New(network, rc, dump)
if err != nil {
panic(err)
}
repeater.Dump = true
panic(repeater.Run())
}

@ -0,0 +1,4 @@
master: brandmeister.pd0zry.ampr.org:62030
authkey: secret
local: 0.0.0.0:62030
localid: 123456

@ -0,0 +1,405 @@
// Package homebrew implements the Home Brew DMR IPSC protocol
package homebrew
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"log"
"net"
"runtime"
"strings"
"sync/atomic"
"time"
)
var (
Version = "20151208"
SoftwareID = fmt.Sprintf("%s:go-dmr:%s", runtime.GOOS, Version)
PackageID = fmt.Sprintf("%s:go-dmr:%s-%s", runtime.GOOS, Version, runtime.GOARCH)
)
type RepeaterConfiguration struct {
Callsign string
RepeaterID uint32
RXFreq uint32
TXFreq uint32
TXPower uint8
ColorCode uint8
Latitude float32
Longitude float32
Height uint16
Location string
Description string
URL string
}
func (r *RepeaterConfiguration) Bytes() []byte {
return []byte(r.String())
}
func (r *RepeaterConfiguration) String() string {
if r.ColorCode < 1 {
r.ColorCode = 1
}
if r.ColorCode > 15 {
r.ColorCode = 15
}
if r.TXPower > 99 {
r.TXPower = 99
}
var lat = fmt.Sprintf("%-08f", r.Latitude)
if len(lat) > 8 {
lat = lat[:8]
}
var lon = fmt.Sprintf("%-09f", r.Longitude)
if len(lon) > 9 {
lon = lon[:9]
}
var b = "RPTC"
b += fmt.Sprintf("%-8s", r.Callsign)
b += fmt.Sprintf("%08x", r.RepeaterID)
b += fmt.Sprintf("%09d", r.RXFreq)
b += fmt.Sprintf("%09d", r.TXFreq)
b += fmt.Sprintf("%02d", r.TXPower)
b += fmt.Sprintf("%02d", r.ColorCode)
b += lat
b += lon
b += fmt.Sprintf("%03d", r.Height)
b += fmt.Sprintf("%-20s", r.Location)
b += fmt.Sprintf("%-20s", r.Description)
b += fmt.Sprintf("%-124s", r.URL)
b += fmt.Sprintf("%-40s", SoftwareID)
b += fmt.Sprintf("%-40s", PackageID)
return b
}
type configFunc func() *RepeaterConfiguration
type CallType byte
const (
GroupCall CallType = iota
UnitCall
)
type FrameType byte
const (
Voice FrameType = iota
VoiceSync
DataSync
UnusedFrameType
)
type Data struct {
Signature [4]byte
Sequence byte
SrcID uint32
DstID uint32
RepeaterID uint32
Flags byte
StreamID uint32
DMR [33]byte
}
func (d *Data) CallType() CallType {
return CallType((d.Flags >> 1) & 0x01)
}
func (d *Data) DataType() byte {
return d.Flags >> 4
}
func (d *Data) FrameType() FrameType {
return FrameType((d.Flags >> 2) & 0x03)
}
func (d *Data) Slot() int {
return int(d.Flags&0x01) + 1
}
func ParseData(data []byte) (*Data, error) {
if len(data) != 53 {
return nil, errors.New("invalid packet length")
}
d := &Data{}
copy(d.Signature[:], data[:4])
d.Sequence = data[4]
d.SrcID = binary.BigEndian.Uint32(append([]byte{0x00}, data[5:7]...))
d.DstID = binary.BigEndian.Uint32(append([]byte{0x00}, data[8:10]...))
d.RepeaterID = binary.BigEndian.Uint32(data[11:15])
d.Flags = data[15]
d.StreamID = binary.BigEndian.Uint32(data[16:20])
copy(d.DMR[:], data[20:])
return d, nil
}
type dataFunc func(*Data)
type authStatus byte
const (
authNone authStatus = iota
authBegin
authDone
authFail
)
type Network struct {
AuthKey string
Local string
LocalID uint32
Master string
MasterID uint32
}
type Link struct {
Dump bool
config configFunc
stream dataFunc
network *Network
conn *net.UDPConn
authKey []byte
local struct {
addr *net.UDPAddr
id []byte
}
master struct {
addr *net.UDPAddr
id []byte
status authStatus
secret []byte
keepalive struct {
outstanding uint32
sent uint64
}
}
}
func New(network *Network, cf configFunc, df dataFunc) (*Link, error) {
if cf == nil {
return nil, errors.New("config func can't be nil")
}
link := &Link{
network: network,
config: cf,
stream: df,
}
var err error
if strings.HasPrefix(network.AuthKey, "0x") {
if link.authKey, err = hex.DecodeString(network.AuthKey[2:]); err != nil {
return nil, err
}
} else {
link.authKey = []byte(network.AuthKey)
}
if network.Local == "" {
network.Local = "0.0.0.0:62030"
}
if network.LocalID == 0 {
return nil, errors.New("missing localid")
}
link.local.id = []byte(fmt.Sprintf("%08x", network.LocalID))
if link.local.addr, err = net.ResolveUDPAddr("udp", network.Local); err != nil {
return nil, err
}
if network.Master == "" {
return nil, errors.New("no master address configured")
}
if link.master.addr, err = net.ResolveUDPAddr("udp", network.Master); err != nil {
return nil, err
}
return link, nil
}
func (l *Link) Run() error {
var err error
if l.conn, err = net.ListenUDP("udp", l.local.addr); err != nil {
return err
}
go l.login()
for {
var (
n int
peer *net.UDPAddr
data = make([]byte, 512)
)
if n, peer, err = l.conn.ReadFromUDP(data); err != nil {
log.Printf("dmr/homebrew: error reading from %s: %v\n", peer, err)
continue
}
go l.parse(peer, data[:n])
}
return nil
}
func (l *Link) Send(addr *net.UDPAddr, data []byte) error {
for len(data) > 0 {
n, err := l.conn.WriteToUDP(data, addr)
if err != nil {
return err
}
data = data[n:]
}
return nil
}
func (l *Link) login() {
var previous = authDone
for l.master.status != authFail {
var p []byte
if l.master.status != previous {
switch l.master.status {
case authNone:
log.Printf("dmr/homebrew: logging in as %d\n", l.network.LocalID)
p = append(RepeaterLogin, l.local.id...)
case authBegin:
log.Printf("dmr/homebrew: authenticating as %d\n", l.network.LocalID)
p = append(RepeaterKey, l.local.id...)
hash := sha256.New()
hash.Write(l.master.secret)
hash.Write(l.authKey)
p = append(p, []byte(hex.EncodeToString(hash.Sum(nil)))...)
case authDone:
config := l.config().Bytes()
fmt.Printf(hex.Dump(config))
log.Printf("dmr/homebrew: logged in, sending %d bytes of repeater configuration\n", len(config))
if err := l.Send(l.master.addr, config); err != nil {
log.Printf("dmr/homebrew: send(%s) failed: %v\n", l.master.addr, err)
return
}
l.keepAlive()
return
case authFail:
log.Println("dmr/homebrew: login failed")
return
}
if p != nil {
l.Send(l.master.addr, p)
}
previous = l.master.status
} else {
log.Println("dmr/homebrew: waiting for master to respond in login sequence...")
time.Sleep(time.Second)
}
}
}
func (l *Link) keepAlive() {
for {
atomic.AddUint32(&l.master.keepalive.outstanding, 1)
atomic.AddUint64(&l.master.keepalive.sent, 1)
var p = append(MasterPing, l.local.id...)
if err := l.Send(l.master.addr, p); err != nil {
log.Printf("dmr/homebrew: send(%s) failed: %v\n", l.master.addr, err)
return
}
time.Sleep(time.Minute)
}
}
func (l *Link) parse(addr *net.UDPAddr, data []byte) {
size := len(data)
switch l.master.status {
case authNone:
if bytes.Equal(data, DMRData) {
return
}
if size < 14 {
return
}
packet := data[:6]
repeater, err := hex.DecodeString(string(data[6:14]))
if err != nil {
log.Println("dmr/homebrew: unexpected login reply from master")
l.master.status = authFail
break
}
switch {
case bytes.Equal(packet, MasterNAK):
log.Printf("dmr/homebrew: login refused by master %d\n", repeater)
l.master.status = authFail
break
case bytes.Equal(packet, MasterACK):
log.Printf("dmr/homebrew: login accepted by master %d\n", repeater)
l.master.secret = data[14:]
l.master.status = authBegin
break
default:
log.Printf("dmr/homebrew: unexpected login reply from master %d\n", repeater)
l.master.status = authFail
break
}
case authBegin:
if bytes.Equal(data, DMRData) {
return
}
if size < 14 {
log.Println("dmr/homebrew: unexpected login reply from master")
l.master.status = authFail
break
}
packet := data[:6]
repeater, err := hex.DecodeString(string(data[6:14]))
if err != nil {
log.Println("dmr/homebrew: unexpected login reply from master")
l.master.status = authFail
break
}
switch {
case bytes.Equal(packet, MasterNAK):
log.Printf("dmr/homebrew: authentication refused by master %d\n", repeater)
l.master.status = authFail
break
case bytes.Equal(packet, MasterACK):
log.Printf("dmr/homebrew: authentication accepted by master %d\n", repeater)
l.master.status = authDone
break
default:
log.Printf("dmr/homebrew: unexpected authentication reply from master %d\n", repeater)
l.master.status = authFail
break
}
case authDone:
switch {
case bytes.Equal(data, DMRData):
if l.stream == nil {
return
}
frame, err := ParseData(data)
if err != nil {
log.Printf("error parsing DMR data: %v\n", err)
return
}
l.stream(frame)
}
}
}

@ -0,0 +1,13 @@
package homebrew
var (
DMRData = []byte("DMRD")
MasterNAK = []byte("MSTNAK")
MasterACK = []byte("MSTACK")
RepeaterLogin = []byte("RPTL")
RepeaterKey = []byte("RPTK")
MasterPing = []byte("MSTPING")
RepeaterPong = []byte("RPTPONG")
MasterClosing = []byte("MSTCL")
RepeaterClosing = []byte("RPTCL")
)

@ -30,9 +30,27 @@ type Network struct {
MasterPeer bool
AuthKey string
Master string
MasterID uint32
Listen string
}
type ipscPeerStatus struct {
connected bool
peerList bool
keepAliveSent int
keepAliveMissed int
keepAliveOutstanding int
keepAliveReceived int
keepAliveRXTime time.Time
}
type ipscPeer struct {
radioID uint32
mode byte
flags []byte
status ipscPeerStatus
}
type IPSC struct {
Network *Network
Dump bool
@ -45,20 +63,13 @@ type IPSC struct {
flags []byte
tsFlags []byte
}
peers map[uint32]ipscPeer
master struct {
addr *net.UDPAddr
radioID []byte
radioID uint32
mode byte
flags []byte
status struct {
connected bool
peerList bool
keepAliveSent int
keepAliveMissed int
keepAliveOutstanding int
keepAliveReceived int
keepAliveRXTime int
}
status ipscPeerStatus
}
conn *net.UDPConn
}
@ -69,7 +80,7 @@ func New(network *Network) (*IPSC, error) {
}
c.local.radioID = make([]byte, 4)
c.local.flags = make([]byte, 4)
c.master.radioID = make([]byte, 4)
c.peers = make(map[uint32]ipscPeer)
c.master.flags = make([]byte, 4)
binary.BigEndian.PutUint32(c.local.radioID, c.Network.RadioID)
@ -180,6 +191,8 @@ func (c *IPSC) Run() error {
log.Printf("authentication failed, dropping packet from %s\n", peer)
continue
}
go c.parse(peer, c.payload(b[:n]))
}
return nil
@ -197,6 +210,37 @@ func (c *IPSC) authenticate(data []byte) bool {
return hmac.Equal(hash, mac.Sum(nil))
}
func (c *IPSC) parse(peer *net.UDPAddr, data []byte) {
packetType := data[0]
peerID := binary.BigEndian.Uint32(data[1:5])
//seq := data[5:6]
switch {
case MasterRequired[packetType]:
if !c.validMaster(peerID) {
log.Printf("%s: peer ID %d is not a valid master, expected %d\n",
peer, peerID, c.Network.MasterID)
return
}
switch packetType {
case MasterAliveReply:
c.resetKeepAlive(peerID)
c.master.status.keepAliveReceived++
c.master.status.keepAliveRXTime = time.Now()
}
case packetType == MasterRegistrationReply:
// We have successfully registered to a master
c.master.radioID = peerID
c.master.mode = data[5]
c.master.flags = data[6:10]
c.master.status.connected = true
c.master.status.keepAliveOutstanding = 0
log.Printf("registered to master %d\n", c.master.radioID)
}
}
func (c *IPSC) payload(data []byte) []byte {
if c.authKey == nil || len(c.authKey) == 0 {
return data
@ -204,6 +248,20 @@ func (c *IPSC) payload(data []byte) []byte {
return data[:len(data)-10]
}
func (c *IPSC) resetKeepAlive(peerID uint32) {
if c.validMaster(peerID) {
c.master.status.keepAliveOutstanding = 0
return
}
if peer, ok := c.peers[peerID]; ok {
peer.status.keepAliveOutstanding = 0
}
}
func (c *IPSC) validMaster(peerID uint32) bool {
return c.master.radioID == peerID
}
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)
@ -292,13 +350,26 @@ func (c *IPSC) peerMaintenance() {
for {
var p []byte
if c.master.status.connected {
log.Println("sending keep-alive to master")
log.Printf("sending keep-alive to master %d\n", c.master.radioID)
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)
if c.master.status.keepAliveOutstanding > 0 {
c.master.status.keepAliveMissed++
log.Printf("%d outstanding keep-alives from master\n", c.master.status.keepAliveOutstanding)
}
if c.master.status.keepAliveOutstanding > c.Network.MaxMissed {
c.master.status.connected = false
c.master.status.keepAliveOutstanding = 0
log.Printf("connection to master %d lost\n", c.master.radioID)
continue
}
c.master.status.keepAliveSent++
c.master.status.keepAliveOutstanding++
} else {
log.Println("registering with master")
r := []byte{MasterRegistrationRequest}

Loading…
Cancel
Save