/** * Digital Voice Modem - Fixed Network Equipment * AGPLv3 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * @package DVM / Fixed Network Equipment * */ /* * Copyright (C) 2022-2023 by Bryan Biedenkapp N2PLL * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. */ using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using System.Text; using System.Text.Json; // thanks .NET Core... using fnecore.DMR; using fnecore.P25; using fnecore.NXDN; using fnecore.EDAC; namespace fnecore { /// /// Event used to process incoming grant request data. /// public class GrantRequestEvent : EventArgs { /// /// Peer ID /// public uint PeerId { get; } /// /// DVM Mode /// public DVMState Mode { get; } /// /// Source Address /// public uint SrcId { get; } /// /// Destination Address /// public uint DstId { get; } /// /// Slot Number /// public byte Slot { get; } /// /// Flag indicating a unit-to-unit (private call) request. /// public bool UnitToUnit { get; } /* ** Methods */ /// /// Initializes a new instance of the class. /// private GrantRequestEvent() { /* stub */ } /// /// Initializes a new instance of the class. /// /// Peer ID /// DVM Mode State /// Source Address /// Destination Address /// Slot Number /// Unit-to-Unit (Private Call) public GrantRequestEvent(uint peerId, DVMState mode, uint srcId, uint dstId, byte slot, bool unitToUnit) : base() { this.PeerId = peerId; this.Mode = mode; this.SrcId = srcId; this.DstId = dstId; this.Slot = slot; this.UnitToUnit = unitToUnit; } } // public class GrantRequestEvent : EventArgs /// /// Implements an FNE "master". /// public class FneMaster : FneBase { private UdpListener server = null; private bool abortListening = false; private CancellationTokenSource listenCancelToken = new CancellationTokenSource(); private Task listenTask = null; private CancellationTokenSource maintainenceCancelToken = new CancellationTokenSource(); private Task maintainenceTask = null; private Dictionary peers; /* ** Properties */ /// /// Gets the for this . /// public IPEndPoint EndPoint { get { if (server != null) return server.EndPoint; return null; } } /// /// Dictionary of connected peers. /// public Dictionary Peers => peers; /// /// Gets/sets the password used for connecting to this master. /// public string Passphrase { get; set; } /// /// Flag indicating whether we are repeating to all connected peers. /// public bool Repeat { get; set; } /// /// Flag indicating whether we are repeating DMR to all connected peers. /// public bool NoRepeatDMR { get; set; } /// /// Flag indicating whether we are repeating P25 to all connected peers. /// public bool NoRepeatP25 { get; set; } /// /// Flag indicating whether we are repeating NXDN to all connected peers. /// public bool NoRepeatNXDN { get; set; } /// /// Gets/sets how many pings are missed before we give up and force a reregister. /// public int MaxMissed { get; set; } /// /// Flag indicating whether peer activity transfers are allowed. /// public bool AllowActivityTransfer { get; set; } /// /// Flag indicating whether peer diagnostic transfers are allowed. /// public bool AllowDiagnosticTransfer { get; set; } /* ** Events/Callbacks */ /// /// Event action that handles processing a grant request. /// public event EventHandler GrantRequestReceived; /// /// Event action that handles peer activity transfers. /// public Action ActivityTransfer = null; /// /// Event action that handles peer diagnostic transfers. /// public Action DiagnosticTransfer = null; /* ** Methods */ /// /// Initializes a new instance of the class. /// /// /// /// public FneMaster(string systemName, uint peerId, int port) : this(systemName, peerId, new IPEndPoint(IPAddress.Any, port)) { /* stub */ } /// /// Initializes a new instance of the class. /// /// /// /// public FneMaster(string systemName, uint peerId, IPEndPoint endpoint) : base(systemName, peerId) { fneType = FneType.MASTER; server = new UdpListener(endpoint); peers = new Dictionary(); Passphrase = string.Empty; Repeat = true; NoRepeatDMR = false; NoRepeatP25 = false; NoRepeatNXDN = false; AllowActivityTransfer = false; AllowDiagnosticTransfer = false; } /// /// Starts the main execution loop for this . /// public override void Start() { if (isStarted) throw new InvalidOperationException("Cannot start listening when already started."); Logger(LogLevel.INFO, $"({systemName}) starting network services, {server.EndPoint}"); abortListening = false; listenTask = Task.Factory.StartNew(Listen, listenCancelToken.Token); maintainenceTask = Task.Factory.StartNew(Maintainence, maintainenceCancelToken.Token); isStarted = true; } /// /// Stops the main execution loop for this . /// public override void Stop() { if (!isStarted) throw new InvalidOperationException("Cannot stop listening when not started."); Logger(LogLevel.INFO, $"({systemName}) stopping network services, {server.EndPoint}"); // stop UDP listen task if (listenTask != null) { abortListening = true; listenCancelToken.Cancel(); try { listenTask.GetAwaiter().GetResult(); } catch (OperationCanceledException) { /* stub */ } finally { listenCancelToken.Dispose(); } } // stop maintainence task if (maintainenceTask != null) { maintainenceCancelToken.Cancel(); try { maintainenceTask.GetAwaiter().GetResult(); } catch (OperationCanceledException) { /* stub */ } finally { maintainenceCancelToken.Dispose(); } } isStarted = false; } /// /// Helper to find and get for the given peer ID. /// /// Peer ID /// if connected, otherwise null. public PeerInformation FindPeer(uint peerId) { if (peers.ContainsKey(peerId)) return peers[peerId]; return null; } /// /// Helper to send a raw UDP frame. /// /// UDP frame to send public void Send(UdpFrame frame) { if (RawPacketTrace) Log(LogLevel.DEBUG, $"({systemName}) Network Sent (to {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}"); server.Send(frame); } /// /// Helper to send a raw UDP frame. /// /// UDP frame to send public async void SendAsync(UdpFrame frame) { if (RawPacketTrace) Log(LogLevel.DEBUG, $"({systemName}) Network Sent (to {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}"); await server.SendAsync(frame); } /// /// Helper to send a raw message to the specified peer. /// /// /// Byte array containing message to send public void SendPeer(IPEndPoint endpoint, byte[] message) { Send(new UdpFrame() { Endpoint = endpoint, Message = message }); } /// /// Helper to send a data message to the specified peer. /// /// Peer ID /// Opcode /// Byte array containing message to send /// /// public void SendPeer(uint peerId, Tuple opcode, byte[] message, ushort pktSeq, uint streamId = 0) { if (peers.ContainsKey(peerId)) { if (streamId == 0) streamId = peers[peerId].StreamID; byte[] data = WriteFrame(message, peerId, this.peerId, opcode, pktSeq, streamId); SendPeer(peers[peerId].EndPoint, data); } } /// /// Helper to send a data message to the specified peer. /// /// Peer ID /// Opcode /// Byte array containing message to send /// public void SendPeer(uint peerId, Tuple opcode, byte[] message, bool incPktSeq = false) { if (peers.ContainsKey(peerId)) { if (incPktSeq) { peers[peerId].PacketSequence = ++peers[peerId].PacketSequence; } SendPeer(peerId, opcode, message, peers[peerId].PacketSequence); } } /// /// Helper to send a command message to the specified peer. /// /// /// Peer ID /// Opcode /// /// /// Byte array containing message to send public void SendPeerCommand(IPEndPoint endpoint, uint peerId, Tuple opcode, ushort pktSeq, uint streamId, byte[] message = null) { int messageLength = 0; if (message != null) messageLength = message.Length; byte[] buffer = new byte[messageLength + 6]; if (message != null) Buffer.BlockCopy(message, 0, buffer, 6, message.Length); byte[] frame = WriteFrame(buffer, peerId, this.peerId, opcode, pktSeq, streamId); SendPeer(endpoint, frame); } /// /// Helper to send a command message to the specified peer. /// /// /// Peer ID /// Opcode /// Byte array containing message to send /// public void SendPeerCommand(uint peerId, Tuple opcode, byte[] message = null, bool incPktSeq = false) { int messageLength = 0; if (message != null) messageLength = message.Length; byte[] buffer = new byte[messageLength + 6]; if (message != null) Buffer.BlockCopy(message, 0, buffer, 6, message.Length); SendPeer(peerId, opcode, buffer, incPktSeq); } /// /// Helper to send a ACK response to the specified peer. /// /// Peer ID public void SendPeerACK(uint peerId) { if (peers.ContainsKey(peerId)) { peers[peerId].PacketSequence = ++peers[peerId].PacketSequence; // send ping response to peer SendPeerCommand(peerId, CreateOpcode(Constants.NET_FUNC_ACK)); } } /// /// Helper to send a NAK response to the specified peer. /// /// Peer ID /// Tag NAK'ed public void SendPeerNAK(uint peerId, string tag) { if (peers.ContainsKey(peerId)) { peers[peerId].PacketSequence = ++peers[peerId].PacketSequence; // send ping response to peer SendPeerCommand(peerId, CreateOpcode(Constants.NET_FUNC_NAK)); Log(LogLevel.WARNING, $"({systemName}) {tag} from unauth PEER {peerId}"); } } /// /// Helper to send a NAK response to the specified endpoint. /// /// IP endpoint /// Tag NAK'ed public void SendNAK(IPEndPoint endpoint, uint peerId, string tag) { byte[] buffer = new byte[10]; FneUtils.WriteBytes(peerId, ref buffer, 6); Send(new UdpFrame() { Endpoint = endpoint, Message = WriteFrame(buffer, peerId, this.peerId, CreateOpcode(Constants.NET_FUNC_NAK), 0, CreateStreamID()) }); Log(LogLevel.WARNING, $"({systemName}) {tag} from unconnected PEER {endpoint.Address.ToString()}:{endpoint.Port}"); } /// /// Helper to send a raw message to the connected peers. /// /// Opcode /// Byte array containing message to send /// RTP Packet Sequence public void SendPeers(Tuple opcode, byte[] message, uint pktSeq = uint.MaxValue) { foreach (PeerInformation peer in peers.Values) { if (pktSeq > ushort.MaxValue) pktSeq = peer.PacketSequence; byte[] data = WriteFrame(message, peer.PeerID, this.peerId, opcode, (ushort)pktSeq, peer.StreamID); SendAsync(new UdpFrame() { Endpoint = peer.EndPoint, Message = data }); } } /// /// Internal helper to compare authorization hashes. /// /// FNE message frame /// Peer Information private bool CompareAuthHash(byte[] message, PeerInformation info) { // get the hash in the frame message byte[] hash = new byte[message.Length - 8]; Buffer.BlockCopy(message, 8, hash, 0, hash.Length); // calculate our own hash byte[] inBuf = new byte[4 + Passphrase.Length]; FneUtils.WriteBytes(info.Salt, ref inBuf, 0); FneUtils.StringToBytes(Passphrase, inBuf, 4, Passphrase.Length); byte[] outHash = FneUtils.sha256_hash(inBuf); // compare hashes if (hash.Length == outHash.Length) { bool res = true; for (int i = 0; i < hash.Length; i++) { if (hash[i] != outHash[i]) { res = false; break; } } return res; } return false; } /// /// /// private async void Listen() { CancellationToken ct = listenCancelToken.Token; ct.ThrowIfCancellationRequested(); while (!abortListening) { try { UdpFrame frame = await server.Receive(); if (RawPacketTrace) Log(LogLevel.DEBUG, $"({systemName}) Network Received (from {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}"); // decode RTP frame if (frame.Message.Length <= 0) continue; RtpHeader rtpHeader; RtpFNEHeader fneHeader; int messageLength = 0; byte[] message = ReadFrame(frame, out messageLength, out rtpHeader, out fneHeader); if (message == null) { Log(LogLevel.ERROR, $"({systemName}) Malformed packet (from {frame.Endpoint}); failed to decode RTP frame"); continue; } if (message.Length < 1) { Log(LogLevel.WARNING, $"({systemName}) Malformed packet (from {frame.Endpoint}) -- {FneUtils.HexDump(message, 0)}"); continue; } uint peerId = fneHeader.PeerID; uint streamId = fneHeader.StreamID; // update current peer stream ID if (peerId > 0 && peers.ContainsKey(peerId) && streamId != 0) { ushort pktSeq = rtpHeader.Sequence; if ((peers[peerId].StreamID == streamId) && (pktSeq != peers[peerId].NextPacketSequence)) Log(LogLevel.WARNING, $"({systemName}) PEER {peerId} Stream {streamId} out-of-sequence; {pktSeq} != {peers[peerId].NextPacketSequence}"); peers[peerId].StreamID = streamId; peers[peerId].PacketSequence = pktSeq; peers[peerId].NextPacketSequence = (ushort)(pktSeq + 1); if (peers[peerId].NextPacketSequence > ushort.MaxValue) peers[peerId].NextPacketSequence = 0; } // if we don't have a stream ID and are receiving call data -- throw an error and discard if (streamId == 0 && fneHeader.Function == Constants.NET_FUNC_PROTOCOL) { Log(LogLevel.ERROR, $"({systemName}) PEER {peerId} Malformed packet (no stream ID for call?)"); continue; } // process incoming message frame opcodes switch (fneHeader.Function) { case Constants.NET_FUNC_PROTOCOL: { if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_DMR) // Encapsulated DMR data frame { if (peerId > 0 && peers.ContainsKey(peerId)) { // validate peer (simple validation really) if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) { byte seqNo = message[4]; uint srcId = FneUtils.Bytes3ToUInt32(message, 5); uint dstId = FneUtils.Bytes3ToUInt32(message, 8); byte bits = message[15]; byte slot = (byte)(((bits & 0x80) == 0x80) ? 1 : 0); CallType callType = ((bits & 0x40) == 0x40) ? CallType.PRIVATE : CallType.GROUP; FrameType frameType = (FrameType)((bits & 0x30) >> 4); DMRDataType dataType = DMRDataType.IDLE; if ((bits & 0x20) == 0x20) dataType = (DMRDataType)(bits & ~(0x20)); byte n = (byte)(bits & 0xF); // is the stream valid? bool ret = true; if (DMRDataValidate != null) ret = DMRDataValidate(peerId, srcId, dstId, slot, callType, frameType, dataType, streamId, message); if (ret) { // is this the peer being ignored? if (PeerIgnored != null) ret = PeerIgnored(peerId, srcId, dstId, slot, callType, frameType, dataType, streamId); else ret = false; if (ret) continue; #if DEBUG Log(LogLevel.DEBUG, $"{systemName} DMRD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} TS {slot} [STREAM ID {streamId}] PKT SEQ {rtpHeader.Sequence}"); #endif // are we repeating to connected peers? if (Repeat && !NoRepeatDMR) { foreach (KeyValuePair kvp in peers) { // don't repeat to the peer sending the data... if (kvp.Key != peerId) { // is this peer being ignored if (PeerIgnored != null) ret = PeerIgnored(peerId, srcId, dstId, slot, callType, frameType, dataType, streamId); else ret = false; if (!ret) SendPeer(kvp.Key, CreateOpcode(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_DMR), message, rtpHeader.Sequence); } } } // perform any userland actions with the data FireDMRDataReceived(new DMRDataReceivedEvent(peerId, srcId, dstId, slot, callType, frameType, dataType, n, rtpHeader.Sequence, streamId, message)); } } else SendPeerNAK(peerId, Constants.TAG_DMR_DATA); } } else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_P25) // Encapsulated P25 data frame { if (peerId > 0 && peers.ContainsKey(peerId)) { // validate peer (simple validation really) if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) { uint srcId = FneUtils.Bytes3ToUInt32(message, 5); uint dstId = FneUtils.Bytes3ToUInt32(message, 8); CallType callType = (message[4] == P25Defines.LC_PRIVATE) ? CallType.PRIVATE : CallType.GROUP; P25DUID duid = (P25DUID)message[22]; FrameType frameType = ((duid != P25DUID.TDU) && (duid != P25DUID.TDULC)) ? FrameType.VOICE : FrameType.TERMINATOR; // is the stream valid? bool ret = true; if (P25DataValidate != null) ret = P25DataValidate(peerId, srcId, dstId, callType, duid, frameType, streamId, message); if (ret) { // pre-process P25 data... FireP25DataPreprocess(new P25DataReceivedEvent(peerId, srcId, dstId, callType, duid, frameType, rtpHeader.Sequence, streamId, message)); // is this the peer being ignored? if (PeerIgnored != null) ret = PeerIgnored(peerId, srcId, dstId, 0, callType, frameType, (frameType == FrameType.VOICE) ? DMRDataType.VOICE_LC_HEADER : DMRDataType.TERMINATOR_WITH_LC, streamId); else ret = false; if (ret) continue; #if DEBUG Log(LogLevel.DEBUG, $"{systemName} P25D: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}] PKT SEQ {rtpHeader.Sequence}"); #endif // are we repeating to connected peers? if (Repeat && !NoRepeatP25) { foreach (KeyValuePair kvp in peers) { // don't repeat to the peer sending the data... if (kvp.Key != peerId) { // is this peer being ignored if (PeerIgnored != null) ret = PeerIgnored(peerId, srcId, dstId, 0, callType, frameType, (frameType == FrameType.VOICE) ? DMRDataType.VOICE_LC_HEADER : DMRDataType.TERMINATOR_WITH_LC, streamId); else ret = false; if (!ret) SendPeer(kvp.Key, CreateOpcode(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), message, rtpHeader.Sequence); } } } // perform any userland actions with the data FireP25DataReceived(new P25DataReceivedEvent(peerId, srcId, dstId, callType, duid, frameType, rtpHeader.Sequence, streamId, message)); } } else SendPeerNAK(peerId, Constants.TAG_P25_DATA); } } else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_NXDN) // Encapsulated NXDN data frame { if (peerId > 0 && peers.ContainsKey(peerId)) { // validate peer (simple validation really) if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) { NXDNMessageType messageType = (NXDNMessageType)message[4]; uint srcId = FneUtils.Bytes3ToUInt32(message, 5); uint dstId = FneUtils.Bytes3ToUInt32(message, 8); byte bits = message[15]; CallType callType = ((bits & 0x40) == 0x40) ? CallType.PRIVATE : CallType.GROUP; FrameType frameType = (messageType != NXDNMessageType.MESSAGE_TYPE_TX_REL) ? FrameType.VOICE : FrameType.TERMINATOR; // is the stream valid? bool ret = true; if (NXDNDataValidate != null) ret = NXDNDataValidate(peerId, srcId, dstId, callType, messageType, frameType, streamId, message); if (ret) { // is this the peer being ignored? if (PeerIgnored != null) ret = PeerIgnored(peerId, srcId, dstId, 0, callType, frameType, (frameType == FrameType.VOICE) ? DMRDataType.VOICE_LC_HEADER : DMRDataType.TERMINATOR_WITH_LC, streamId); else ret = false; if (ret) continue; #if DEBUG Log(LogLevel.DEBUG, $"{systemName} NXDD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}] PKT SEQ {rtpHeader.Sequence}"); #endif // are we repeating to connected peers? if (Repeat && !NoRepeatNXDN) { foreach (KeyValuePair kvp in peers) { // don't repeat to the peer sending the data... if (kvp.Key != peerId) { // is this peer being ignored if (PeerIgnored != null) ret = PeerIgnored(peerId, srcId, dstId, 0, callType, frameType, (frameType == FrameType.VOICE) ? DMRDataType.VOICE_LC_HEADER : DMRDataType.TERMINATOR_WITH_LC, streamId); else ret = false; if (!ret) SendPeer(kvp.Key, CreateOpcode(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_NXDN), message, rtpHeader.Sequence); } } } // perform any userland actions with the data FireNXDNDataReceived(new NXDNDataReceivedEvent(peerId, srcId, dstId, callType, messageType, frameType, rtpHeader.Sequence, streamId, message)); } } else SendPeerNAK(peerId, Constants.TAG_NXDN_DATA); } } else Log(LogLevel.ERROR, $"({systemName}) Unknown protocol opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}"); } break; case Constants.NET_FUNC_RPTL: // Repeater Login { if (peerId > 0 && !peers.ContainsKey(peerId)) { PeerInformation info = new PeerInformation(); info.PeerID = peerId; info.EndPoint = frame.Endpoint; info.PacketSequence = rtpHeader.Sequence; info.NextPacketSequence = ++rtpHeader.Sequence; info.StreamID = streamId; info.Salt = (uint)rand.Next(-2147483648, 2147483647); Log(LogLevel.INFO, $"({systemName}) Repeater logging in with PEER {peerId}, {info.EndPoint}"); byte[] buffer = new byte[4]; FneUtils.WriteBytes(info.Salt, ref buffer, 0); SendPeerCommand(frame.Endpoint, peerId, CreateOpcode(Constants.NET_FUNC_ACK), ++info.PacketSequence, streamId, buffer); info.State = ConnectionState.WAITING_AUTHORISATION; peers.Add(peerId, info); Log(LogLevel.INFO, $"({systemName}) Challenge Response sent to PEER {peerId} for login {info.Salt}"); } else { SendNAK(frame.Endpoint, peerId, Constants.TAG_REPEATER_LOGIN); // check if the peer is in our peer list -- if he is, and he isn't in a running state, reset // the login sequence if (peerId > 0 && !peers.ContainsKey(peerId)) { PeerInformation info = new PeerInformation(); if (info.State != ConnectionState.RUNNING) if (peers.ContainsKey(peerId)) peers.Remove(peerId); } } } break; case Constants.NET_FUNC_RPTK: // Repeater Authentication { if (peerId > 0 && peers.ContainsKey(peerId)) { PeerInformation info = peers[peerId]; info.LastPing = DateTime.Now; if (info.State == ConnectionState.WAITING_AUTHORISATION) { if (CompareAuthHash(message, info)) { info.State = ConnectionState.WAITING_CONFIG; SendPeerACK(peerId); peers[peerId] = info; Log(LogLevel.INFO, $"({systemName}) PEER {peerId} has completed the login exchange"); } else { Log(LogLevel.WARNING, $"({systemName}) PEER {peerId} has failed the login exchange"); SendPeerNAK(peerId, Constants.TAG_REPEATER_AUTH); if (peers.ContainsKey(peerId)) peers.Remove(peerId); } } else { Log(LogLevel.WARNING, $"({systemName}) PEER {peerId} tried login exchange in wrong state"); SendPeerNAK(peerId, Constants.TAG_REPEATER_AUTH); if (peers.ContainsKey(peerId)) peers.Remove(peerId); } } else SendNAK(frame.Endpoint, peerId, Constants.TAG_REPEATER_AUTH); } break; case Constants.NET_FUNC_RPTC: // Repeater Configuration { if (peerId > 0 && peers.ContainsKey(peerId)) { PeerInformation info = peers[peerId]; info.LastPing = DateTime.Now; if (info.State == ConnectionState.WAITING_CONFIG) { string payload = FneUtils.BytesToString(message, 8, message.Length - 8); try { JsonDocument json = JsonDocument.Parse(payload); // identity info.Details.Identity = json.RootElement.GetProperty("identity").GetString(); info.Details.RxFrequency = json.RootElement.GetProperty("rxFrequency").GetUInt32(); info.Details.TxFrequency = json.RootElement.GetProperty("txFrequency").GetUInt32(); // system info JsonElement sysInfo = json.RootElement.GetProperty("info"); info.Details.Latitude = sysInfo.GetProperty("latitude").GetDouble(); info.Details.Longitude = sysInfo.GetProperty("longitude").GetDouble(); info.Details.Height = sysInfo.GetProperty("height").GetInt32(); info.Details.Location = sysInfo.GetProperty("location").GetString(); // channel data JsonElement channel = json.RootElement.GetProperty("channel"); info.Details.TxPower = channel.GetProperty("txPower").GetUInt32(); info.Details.TxOffsetMhz = (float)channel.GetProperty("txOffsetMhz").GetDouble(); info.Details.ChBandwidthKhz = (float)channel.GetProperty("chBandwidthKhz").GetDouble(); info.Details.ChannelID = channel.GetProperty("channelId").GetByte(); info.Details.ChannelNo = channel.GetProperty("channelNo").GetUInt32(); // RCON JsonElement rcon = json.RootElement.GetProperty("rcon"); info.Details.Password = rcon.GetProperty("password").GetString(); info.Details.Port = rcon.GetProperty("port").GetInt32(); info.Details.Software = json.RootElement.GetProperty("software").GetString(); } catch { const string outOfDate = "Old/Out of Date Peer"; // identity info.Details.Identity = outOfDate; info.Details.RxFrequency = 0; info.Details.TxFrequency = 0; // system info info.Details.Latitude = 0.0d; info.Details.Longitude = 0.0d; info.Details.Height = 0; info.Details.Location = outOfDate; // channel data info.Details.TxOffsetMhz = 0.0f; info.Details.ChBandwidthKhz = 0.0f; info.Details.ChannelID = 0; info.Details.ChannelNo = 0; info.Details.TxPower = 0; // RCON info.Details.Password = "ABCD1234"; info.Details.Port = 9990; // default port info.Details.Software = "Peer Software Did Not Send JSON Configuration"; } info.State = ConnectionState.RUNNING; info.Connection = true; info.PingsReceived = 0; info.LastPing = DateTime.Now; SendPeerACK(peerId); Log(LogLevel.INFO, $"({systemName}) PEER {peerId} has completed the configuration exchange"); peers[peerId] = info; // userland actions FirePeerConnected(new PeerConnectedEvent(peerId, info)); } else { Log(LogLevel.WARNING, $"({systemName}) PEER {peerId} tried configuration exchange in wrong state"); SendPeerNAK(peerId, Constants.TAG_REPEATER_CONFIG); if (peers.ContainsKey(peerId)) peers.Remove(peerId); } } else SendNAK(frame.Endpoint, peerId, Constants.TAG_REPEATER_CONFIG); } break; case Constants.NET_FUNC_RPT_CLOSING: // Repeater Closing (Disconnect) { if (peerId > 0 && peers.ContainsKey(peerId)) { // validate peer (simple validation really) if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) { Log(LogLevel.INFO, $"({systemName}) PEER {peerId} is closing down"); SendPeerACK(peerId); peers.Remove(peerId); // userland actions if (PeerDisconnected != null) PeerDisconnected(peerId); } } } break; case Constants.NET_FUNC_PING: // Repeater Ping { if (peerId > 0 && peers.ContainsKey(peerId)) { // validate peer (simple validation really) if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) { PeerInformation peer = peers[peerId]; peer.PingsReceived++; peer.LastPing = DateTime.Now; //peer.PacketSequence = ++peer.PacketSequence; peers[peerId] = peer; // send ping response to peer SendPeerCommand(peerId, CreateOpcode(Constants.NET_FUNC_PONG), null, true); Log(LogLevel.DEBUG, $"({systemName}) Received and answered RPTPING from PEER {peerId}"); } else SendPeerNAK(peerId, Constants.TAG_REPEATER_PING); } } break; case Constants.NET_FUNC_GRANT: // Repeater Grant Request { if (peerId > 0 && peers.ContainsKey(peerId)) { // validate peer (simple validation really) if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) { uint srcId = FneUtils.ToUInt32(message, 11); uint dstId = FneUtils.ToUInt32(message, 15); byte slot = (byte)(message[19] & 0x07); bool unitToUnit = (bool)((message[19] & 0x80) == 0x80); DVMState mode = (DVMState)message[20]; if (GrantRequestReceived != null) GrantRequestReceived(this, new GrantRequestEvent(peerId, mode, srcId, dstId, slot, unitToUnit)); } else SendPeerNAK(peerId, Constants.TAG_REPEATER_GRANT); } } break; case Constants.NET_FUNC_TRANSFER: { if (fneHeader.SubFunction == Constants.NET_TRANSFER_SUBFUNC_ACTIVITY) // Peer Activity Log Transfer { // can we do activity transfers? if (AllowActivityTransfer) { if (peerId > 0 && peers.ContainsKey(peerId)) { // validate peer (simple validation really if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) { byte[] buffer = new byte[messageLength - 11]; Buffer.BlockCopy(message, 11, buffer, 0, buffer.Length); string msg = Encoding.ASCII.GetString(buffer); if (ActivityTransfer != null) ActivityTransfer(peerId, msg); } } } } else if (fneHeader.SubFunction == Constants.NET_TRANSFER_SUBFUNC_DIAG) // Peer Diagnostic Log Transfer { // can we do diagnostic transfers? if (AllowDiagnosticTransfer) { if (peerId > 0 && peers.ContainsKey(peerId)) { // validate peer (simple validation really) if (peers[peerId].Connection && peers[peerId].EndPoint.ToString() == frame.Endpoint.ToString()) { byte[] buffer = new byte[messageLength - 11]; Buffer.BlockCopy(message, 11, buffer, 0, buffer.Length); string msg = Encoding.ASCII.GetString(buffer); if (DiagnosticTransfer != null) DiagnosticTransfer(peerId, msg); } } } } else Log(LogLevel.ERROR, $"({systemName}) Unknown transfer opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}"); } break; default: Log(LogLevel.ERROR, $"({systemName}) Unknown opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}"); break; } } catch (SocketException se) { Log(LogLevel.FATAL, $"({systemName}) SOCKET ERROR: {se.SocketErrorCode}; {se.Message}"); } if (ct.IsCancellationRequested) abortListening = true; } } /// /// Internal maintainence routine. /// private async void Maintainence() { CancellationToken ct = maintainenceCancelToken.Token; while (!abortListening) { lock (peers) { // check to see if any peers have been quiet (no ping) longer than allowed List peersToRemove = new List(); foreach (KeyValuePair kvp in peers) { uint peerId = kvp.Key; PeerInformation peer = kvp.Value; DateTime dt = peer.LastPing.AddSeconds(PingTime * MaxMissed); if (dt < DateTime.Now) { Log(LogLevel.INFO, $"({systemName}) PEER {peerId} has timed out"); peersToRemove.Add(peerId); } } // remove any peers foreach (uint peerId in peersToRemove) peers.Remove(peerId); } try { await Task.Delay(PingTime * 1000, ct); } catch (TaskCanceledException) { /* stub */ } } } } // public class FneMaster } // namespace fnecore