You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fnecore/FneMaster.cs

1235 lines
59 KiB

// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - Fixed Network Equipment Core Library
* 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 Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL
*
*/
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
{
/// <summary>
/// Event used to process incoming grant request data.
/// </summary>
public class GrantRequestEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// DVM Mode
/// </summary>
public DVMState Mode { get; }
/// <summary>
/// Source Address
/// </summary>
public uint SrcId { get; }
/// <summary>
/// Destination Address
/// </summary>
public uint DstId { get; }
/// <summary>
/// Slot Number
/// </summary>
public byte Slot { get; }
/// <summary>
/// Flag indicating a unit-to-unit (private call) request.
/// </summary>
public bool UnitToUnit { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="GrantRequestEvent"/> class.
/// </summary>
private GrantRequestEvent()
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="GrantRequestEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="mode">DVM Mode State</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="slot">Slot Number</param>
/// <param name="unitToUnit">Unit-to-Unit (Private Call)</param>
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
/// <summary>
/// Implements an FNE "master".
/// </summary>
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<uint, PeerInformation> peers;
/*
** Properties
*/
/// <summary>
/// Gets the <see cref="IPEndPoint"/> for this <see cref="FneMaster"/>.
/// </summary>
public IPEndPoint EndPoint
{
get
{
if (server != null)
return server.EndPoint;
return null;
}
}
/// <summary>
/// Dictionary of connected peers.
/// </summary>
public Dictionary<uint, PeerInformation> Peers => peers;
/// <summary>
/// Gets/sets the password used for connecting to this master.
/// </summary>
public string Passphrase
{
get;
set;
}
/// <summary>
/// Flag indicating whether we are repeating to all connected peers.
/// </summary>
public bool Repeat
{
get;
set;
}
/// <summary>
/// Flag indicating whether we are repeating DMR to all connected peers.
/// </summary>
public bool NoRepeatDMR
{
get;
set;
}
/// <summary>
/// Flag indicating whether we are repeating P25 to all connected peers.
/// </summary>
public bool NoRepeatP25
{
get;
set;
}
/// <summary>
/// Flag indicating whether we are repeating NXDN to all connected peers.
/// </summary>
public bool NoRepeatNXDN
{
get;
set;
}
/// <summary>
/// Gets/sets how many pings are missed before we give up and force a reregister.
/// </summary>
public int MaxMissed
{
get;
set;
}
/// <summary>
/// Flag indicating whether peer activity transfers are allowed.
/// </summary>
public bool AllowActivityTransfer
{
get;
set;
}
/// <summary>
/// Flag indicating whether peer diagnostic transfers are allowed.
/// </summary>
public bool AllowDiagnosticTransfer
{
get;
set;
}
/*
** Events/Callbacks
*/
/// <summary>
/// Event action that handles processing a grant request.
/// </summary>
public event EventHandler<GrantRequestEvent> GrantRequestReceived;
/// <summary>
/// Event action that handles peer activity transfers.
/// </summary>
public Action<uint, string> ActivityTransfer = null;
/// <summary>
/// Event action that handles peer diagnostic transfers.
/// </summary>
public Action<uint, string> DiagnosticTransfer = null;
/// <summary>
/// Event action that handles peer unit registration announcements.
/// </summary>
public Action<uint, uint> UnitRegistration = null;
/// <summary>
/// Event action that handles peer unit deregistration announcements
/// </summary>
public Action<uint, uint> UnitDeregistration = null;
/// <summary>
/// Event action that handles peer group affiliation announcements
/// </summary>
public Action<uint, uint, uint> UnitGroupAffiliation = null;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="FneMaster"/> class.
/// </summary>
/// <param name="systemName"></param>
/// <param name="peerId"></param>
/// <param name="port"></param>
public FneMaster(string systemName, uint peerId, int port) : this(systemName, peerId, new IPEndPoint(IPAddress.Any, port))
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="FneMaster"/> class.
/// </summary>
/// <param name="systemName"></param>
/// <param name="peerId"></param>
/// <param name="endpoint"></param>
public FneMaster(string systemName, uint peerId, IPEndPoint endpoint) : base(systemName, peerId)
{
fneType = FneType.MASTER;
server = new UdpListener(endpoint);
peers = new Dictionary<uint, PeerInformation>();
Passphrase = string.Empty;
Repeat = true;
NoRepeatDMR = false;
NoRepeatP25 = false;
NoRepeatNXDN = false;
AllowActivityTransfer = false;
AllowDiagnosticTransfer = false;
}
/// <summary>
/// Starts the main execution loop for this <see cref="FneMaster"/>.
/// </summary>
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;
}
/// <summary>
/// Stops the main execution loop for this <see cref="FneMaster"/>.
/// </summary>
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;
}
/// <summary>
/// Helper to find and get <see cref="PeerInformation"/> for the given peer ID.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <returns><see cref="PeerInformation"/> if connected, otherwise null.</returns>
public PeerInformation FindPeer(uint peerId)
{
if (peers.ContainsKey(peerId))
return peers[peerId];
return null;
}
/// <summary>
/// Helper to send a raw UDP frame.
/// </summary>
/// <param name="frame">UDP frame to send</param>
public void Send(UdpFrame frame)
{
if (RawPacketTrace)
Log(LogLevel.DEBUG, $"({systemName}) Network Sent (to {frame.Endpoint}) -- {FneUtils.HexDump(frame.Message, 0)}");
server.Send(frame);
}
/// <summary>
/// Helper to send a raw UDP frame.
/// </summary>
/// <param name="frame">UDP frame to send</param>
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);
}
/// <summary>
/// Helper to send a raw message to the specified peer.
/// </summary>
/// <param name="endpoint"><see cref="IPEndPoint"/></param>
/// <param name="message">Byte array containing message to send</param>
public void SendPeer(IPEndPoint endpoint, byte[] message)
{
Send(new UdpFrame()
{
Endpoint = endpoint,
Message = message
});
}
/// <summary>
/// Helper to send a data message to the specified peer.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="opcode">Opcode</param>
/// <param name="message">Byte array containing message to send</param>
/// <param name="pktSeq"></param>
/// <param name="streamId"></param>
public void SendPeer(uint peerId, Tuple<byte, byte> 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);
}
}
/// <summary>
/// Helper to send a data message to the specified peer.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="opcode">Opcode</param>
/// <param name="message">Byte array containing message to send</param>
/// <param name="incPktSeq"></param>
public void SendPeer(uint peerId, Tuple<byte, byte> 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);
}
}
/// <summary>
/// Helper to send a command message to the specified peer.
/// </summary>
/// <param name="endpoint"><see cref="IPEndPoint"/></param>
/// <param name="peerId">Peer ID</param>
/// <param name="opcode">Opcode</param>
/// <param name="pktSeq"></param>
/// <param name="streamId"></param>
/// <param name="message">Byte array containing message to send</param>
public void SendPeerCommand(IPEndPoint endpoint, uint peerId, Tuple<byte, byte> 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);
}
/// <summary>
/// Helper to send a command message to the specified peer.
/// </summary>
/// <param name="endpoint"><see cref="IPEndPoint"/></param>
/// <param name="peerId">Peer ID</param>
/// <param name="opcode">Opcode</param>
/// <param name="message">Byte array containing message to send</param>
/// <param name="incPktSeq"></param>
public void SendPeerCommand(uint peerId, Tuple<byte, byte> 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);
}
/// <summary>
/// Helper to send a ACK response to the specified peer.
/// </summary>
/// <param name="peerId">Peer ID</param>
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));
}
}
/// <summary>
/// Helper to send a NAK response to the specified peer.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="tag">Tag NAK'ed</param>
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}");
}
}
/// <summary>
/// Helper to send a NAK response to the specified endpoint.
/// </summary>
/// <param name="endpoint">IP endpoint</param>
/// <param name="tag">Tag NAK'ed</param>
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}");
}
/// <summary>
/// Helper to send a raw message to the connected peers.
/// </summary>
/// <param name="opcode">Opcode</param>
/// <param name="message">Byte array containing message to send</param>
/// <param name="pktSeq">RTP Packet Sequence</param>
public void SendPeers(Tuple<byte, byte> 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
});
}
}
/// <summary>
/// Internal helper to compare authorization hashes.
/// </summary>
/// <param name="message">FNE message frame</param>
/// <param name="info">Peer Information</param>
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;
}
/// <summary>
///
/// </summary>
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<uint, PeerInformation> 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<uint, PeerInformation> 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<uint, PeerInformation> 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;
case Constants.NET_FUNC_ANNOUNCE:
{
if (fneHeader.SubFunction == Constants.NET_ANNC_SUBFUNC_GRP_AFFIL) // Announce Group Affiliation
{
// 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())
{
uint srcId = FneUtils.Bytes3ToUInt32(message, 0);
uint dstId = FneUtils.Bytes3ToUInt32(message, 3);
if (UnitGroupAffiliation != null)
UnitGroupAffiliation(peerId, srcId, dstId);
}
}
}
}
else if (fneHeader.SubFunction == Constants.NET_ANNC_SUBFUNC_UNIT_REG) // Announce Unit Registration
{
// 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())
{
uint srcId = FneUtils.Bytes3ToUInt32(message, 0);
if (UnitRegistration != null)
UnitRegistration(peerId, srcId);
}
}
}
}
else if (fneHeader.SubFunction == Constants.NET_ANNC_SUBFUNC_UNIT_DEREG) // Announce Unit Deregistration
{
// 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())
{
uint srcId = FneUtils.Bytes3ToUInt32(message, 0);
if (UnitDeregistration != null)
UnitDeregistration(peerId, srcId);
}
}
}
}
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;
}
}
/// <summary>
/// Internal maintainence routine.
/// </summary>
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<uint> peersToRemove = new List<uint>();
foreach (KeyValuePair<uint, PeerInformation> 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

Powered by TurnKey Linux.