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