|
|
|
|
@ -7,7 +7,7 @@
|
|
|
|
|
* @package DVM / Fixed Network Equipment Core Library
|
|
|
|
|
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
|
|
|
|
|
*
|
|
|
|
|
* Copyright (C) 2022-2023 Bryan Biedenkapp, N2PLL
|
|
|
|
|
* Copyright (C) 2022-2025 Bryan Biedenkapp, N2PLL
|
|
|
|
|
* Copyright (C) 2024 Caleb, KO4UYJ
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
@ -26,8 +26,8 @@ using System.Collections.Generic;
|
|
|
|
|
using fnecore.DMR;
|
|
|
|
|
using fnecore.P25;
|
|
|
|
|
using fnecore.NXDN;
|
|
|
|
|
using fnecore.Analog;
|
|
|
|
|
using fnecore.P25.KMM;
|
|
|
|
|
using System.Net.NetworkInformation;
|
|
|
|
|
|
|
|
|
|
namespace fnecore
|
|
|
|
|
{
|
|
|
|
|
@ -39,6 +39,18 @@ namespace fnecore
|
|
|
|
|
/// <param name="streamId">Stream ID</param>
|
|
|
|
|
/// <returns>True, if the frame was handled, otherwise false.</returns>
|
|
|
|
|
public delegate bool RawNetworkFrame(UdpFrame frame, uint peerId, uint streamId);
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Callback used to process a raw network frame.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="frame"><see cref="UdpFrame"/></param>
|
|
|
|
|
/// <param name="peerId">Peer ID</param>
|
|
|
|
|
/// <param name="streamId">Stream ID</param>
|
|
|
|
|
/// <param name="rtpHeader">RTP Header</param>
|
|
|
|
|
/// <param name="fneHeader">RTP FNE Header</param>
|
|
|
|
|
/// <param name="message">Raw packet message</param>
|
|
|
|
|
/// <returns>True, if the frame was handled, otherwise false.</returns>
|
|
|
|
|
public delegate bool UserPacketHandler(UdpFrame frame, uint peerId, uint streamId,
|
|
|
|
|
RtpHeader rtpHeader, RtpFNEHeader fneHeader, byte[] message);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Implements an FNE "peer".
|
|
|
|
|
@ -62,6 +74,15 @@ namespace fnecore
|
|
|
|
|
private ushort currPktSeq = 0;
|
|
|
|
|
private uint streamId = 0;
|
|
|
|
|
|
|
|
|
|
private List<TalkgroupEntry> announcedTGs = new List<TalkgroupEntry>();
|
|
|
|
|
private List<PeerHAIPEntry> haIPs = new List<PeerHAIPEntry>();
|
|
|
|
|
private int currentHAIP;
|
|
|
|
|
|
|
|
|
|
private int retryCount;
|
|
|
|
|
private int maxRetryCount = Constants.MAX_RETRY_BEFORE_RECONNECT;
|
|
|
|
|
|
|
|
|
|
private bool trafficLogging;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Properties
|
|
|
|
|
*/
|
|
|
|
|
@ -84,6 +105,11 @@ namespace fnecore
|
|
|
|
|
set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the list of talkgroups announced from the master FNE.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public List<TalkgroupEntry> AnnouncedTGs => announcedTGs;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the number of pings sent.
|
|
|
|
|
/// </summary>
|
|
|
|
|
@ -102,6 +128,16 @@ namespace fnecore
|
|
|
|
|
private set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets/sets flag indicating whether or not the user packet handler is also handling
|
|
|
|
|
/// protocol packets.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool UserProtocolHandler
|
|
|
|
|
{
|
|
|
|
|
get;
|
|
|
|
|
set;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Events/Callbacks
|
|
|
|
|
*/
|
|
|
|
|
@ -110,6 +146,10 @@ namespace fnecore
|
|
|
|
|
/// Event action that handles a raw network frame directly.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public RawNetworkFrame NetworkFrameHandler = null;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Event action that handles a RTP network packet.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public UserPacketHandler UserPacketHandler = null;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
** Methods
|
|
|
|
|
@ -122,8 +162,9 @@ namespace fnecore
|
|
|
|
|
/// <param name="peerId"></param>
|
|
|
|
|
/// <param name="address"></param>
|
|
|
|
|
/// <param name="port"></param>
|
|
|
|
|
public FnePeer(string systemName, uint peerId, string address, int port, string PresharedKey = null) : this(systemName, peerId, new IPEndPoint(IPAddress.Parse(address), port), PresharedKey)
|
|
|
|
|
public FnePeer(string systemName, uint peerId, string address, int port, string PresharedKey = null, bool trafficLogging = false) : this(systemName, peerId, new IPEndPoint(IPAddress.Parse(address), port), PresharedKey)
|
|
|
|
|
{
|
|
|
|
|
this.trafficLogging = trafficLogging;
|
|
|
|
|
/* stub */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -133,7 +174,7 @@ namespace fnecore
|
|
|
|
|
/// <param name="systemName"></param>
|
|
|
|
|
/// <param name="peerId"></param>
|
|
|
|
|
/// <param name="endpoint"></param>
|
|
|
|
|
public FnePeer(string systemName, uint peerId, IPEndPoint endpoint, string PresharedKey = null) : base(systemName, peerId)
|
|
|
|
|
public FnePeer(string systemName, uint peerId, IPEndPoint endpoint, string PresharedKey = null, bool trafficLogging = false) : base(systemName, peerId)
|
|
|
|
|
{
|
|
|
|
|
masterEndpoint = endpoint;
|
|
|
|
|
client = new UdpReceiver();
|
|
|
|
|
@ -145,7 +186,11 @@ namespace fnecore
|
|
|
|
|
info.PeerID = peerId;
|
|
|
|
|
info.State = ConnectionState.WAITING_LOGIN;
|
|
|
|
|
|
|
|
|
|
UserProtocolHandler = false;
|
|
|
|
|
|
|
|
|
|
PingsAcked = 0;
|
|
|
|
|
|
|
|
|
|
this.trafficLogging = trafficLogging;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
@ -402,6 +447,30 @@ namespace fnecore
|
|
|
|
|
return curr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Helper to rotate the master endpoint to the next HA endpoint.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void RotateMasterEndpont()
|
|
|
|
|
{
|
|
|
|
|
// are we rotating IPs for HA reconnect?
|
|
|
|
|
if (haIPs.Count() > 0 && retryCount > 0U && maxRetryCount == Constants.MAX_RETRY_HA_RECONNECT)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
PeerHAIPEntry entry = haIPs[currentHAIP];
|
|
|
|
|
currentHAIP++;
|
|
|
|
|
|
|
|
|
|
if (currentHAIP > haIPs.Count)
|
|
|
|
|
{
|
|
|
|
|
currentHAIP = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; trying next HA {entry.EndPoint}...");
|
|
|
|
|
masterEndpoint = entry.EndPoint;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
++retryCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Internal UDP listen routine.
|
|
|
|
|
/// </summary>
|
|
|
|
|
@ -461,7 +530,14 @@ namespace fnecore
|
|
|
|
|
{
|
|
|
|
|
case Constants.NET_FUNC_PROTOCOL:
|
|
|
|
|
{
|
|
|
|
|
if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_DMR) // Encapsulated DMR data frame
|
|
|
|
|
// are we handling protocol packets with the user packet handler?
|
|
|
|
|
if (UserPacketHandler != null && UserProtocolHandler)
|
|
|
|
|
{
|
|
|
|
|
UserPacketHandler(frame, peerId, streamId, rtpHeader, fneHeader, message);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_DMR) // Encapsulated DMR data frame
|
|
|
|
|
{
|
|
|
|
|
if (peerId != this.peerId)
|
|
|
|
|
{
|
|
|
|
|
@ -472,6 +548,8 @@ namespace fnecore
|
|
|
|
|
// is this for our peer?
|
|
|
|
|
if (peerId == this.peerId)
|
|
|
|
|
{
|
|
|
|
|
// TODO: port the mux validation logic from dvmhost:src/common/network/Network.cpp
|
|
|
|
|
|
|
|
|
|
byte seqNo = message[4];
|
|
|
|
|
uint srcId = FneUtils.Bytes3ToUInt32(message, 5);
|
|
|
|
|
uint dstId = FneUtils.Bytes3ToUInt32(message, 8);
|
|
|
|
|
@ -487,13 +565,14 @@ namespace fnecore
|
|
|
|
|
|
|
|
|
|
byte n = (byte)(bits & 0xF);
|
|
|
|
|
#if DEBUG
|
|
|
|
|
Log(LogLevel.DEBUG, $"{systemName} DMRD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} TS {slot} [STREAM ID {streamId}]");
|
|
|
|
|
if (trafficLogging)
|
|
|
|
|
Log(LogLevel.DEBUG, $"{systemName} DMRD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} TS {slot} [STREAM ID {streamId}]");
|
|
|
|
|
#endif
|
|
|
|
|
// perform any userland actions with the data
|
|
|
|
|
FireDMRDataReceived(new DMRDataReceivedEvent(peerId, srcId, dstId, slot, callType, frameType, dataType, n, rtpHeader.Sequence, streamId, message));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_P25) // Encapsulated P25 data frame
|
|
|
|
|
else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_P25) // Encapsulated P25 data frame
|
|
|
|
|
{
|
|
|
|
|
if (peerId != this.peerId)
|
|
|
|
|
{
|
|
|
|
|
@ -504,19 +583,22 @@ namespace fnecore
|
|
|
|
|
// is this for our peer?
|
|
|
|
|
if (peerId == this.peerId)
|
|
|
|
|
{
|
|
|
|
|
// TODO: port the mux validation logic from dvmhost:src/common/network/Network.cpp
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
#if DEBUG
|
|
|
|
|
Log(LogLevel.DEBUG, $"{systemName} P25D: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}]");
|
|
|
|
|
if (trafficLogging)
|
|
|
|
|
Log(LogLevel.DEBUG, $"{systemName} P25D: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}]");
|
|
|
|
|
#endif
|
|
|
|
|
// perform any userland actions with the data
|
|
|
|
|
FireP25DataReceived(new P25DataReceivedEvent(peerId, srcId, dstId, callType, duid, frameType, rtpHeader.Sequence, streamId, message));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_NXDN) // Encapsulated NXDN data frame
|
|
|
|
|
else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_NXDN) // Encapsulated NXDN data frame
|
|
|
|
|
{
|
|
|
|
|
if (peerId != this.peerId)
|
|
|
|
|
{
|
|
|
|
|
@ -527,6 +609,8 @@ namespace fnecore
|
|
|
|
|
// is this for our peer?
|
|
|
|
|
if (peerId == this.peerId)
|
|
|
|
|
{
|
|
|
|
|
// TODO: port the mux validation logic from dvmhost:src/common/network/Network.cpp
|
|
|
|
|
|
|
|
|
|
NXDNMessageType messageType = (NXDNMessageType)message[4];
|
|
|
|
|
uint srcId = FneUtils.Bytes3ToUInt32(message, 5);
|
|
|
|
|
uint dstId = FneUtils.Bytes3ToUInt32(message, 8);
|
|
|
|
|
@ -535,22 +619,211 @@ namespace fnecore
|
|
|
|
|
CallType callType = ((bits & 0x40) == 0x40) ? CallType.PRIVATE : CallType.GROUP;
|
|
|
|
|
FrameType frameType = (messageType != NXDNMessageType.MESSAGE_TYPE_TX_REL) ? FrameType.VOICE : FrameType.TERMINATOR;
|
|
|
|
|
#if DEBUG
|
|
|
|
|
Log(LogLevel.DEBUG, $"{systemName} NXDD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}]");
|
|
|
|
|
if (trafficLogging)
|
|
|
|
|
Log(LogLevel.DEBUG, $"{systemName} NXDD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}]");
|
|
|
|
|
#endif
|
|
|
|
|
// perform any userland actions with the data
|
|
|
|
|
FireNXDNDataReceived(new NXDNDataReceivedEvent(peerId, srcId, dstId, callType, messageType, frameType, rtpHeader.Sequence, streamId, message));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_ANALOG) // Encapsulated Analog data frame
|
|
|
|
|
{
|
|
|
|
|
if (peerId != this.peerId)
|
|
|
|
|
{
|
|
|
|
|
//Log(LogLevel.WARNING, $"({systemName}) PEER {peerId}; routed traffic, rewriting PEER {this.peerId}");
|
|
|
|
|
peerId = this.peerId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// is this for our peer?
|
|
|
|
|
if (peerId == this.peerId)
|
|
|
|
|
{
|
|
|
|
|
// TODO: port the mux validation logic from dvmhost:src/common/network/Network.cpp
|
|
|
|
|
|
|
|
|
|
uint srcId = FneUtils.Bytes3ToUInt32(message, 5);
|
|
|
|
|
uint dstId = FneUtils.Bytes3ToUInt32(message, 8);
|
|
|
|
|
CallType callType = CallType.GROUP; /* analog calls cannot be private calls right now ... */
|
|
|
|
|
AudioFrameType audioFrameType = (AudioFrameType)(message[15] & 0x0F);
|
|
|
|
|
FrameType frameType = (audioFrameType != AudioFrameType.TERMINATOR) ? FrameType.VOICE : FrameType.TERMINATOR;
|
|
|
|
|
#if DEBUG
|
|
|
|
|
if (trafficLogging)
|
|
|
|
|
Log(LogLevel.DEBUG, $"{systemName} P25D: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}]");
|
|
|
|
|
#endif
|
|
|
|
|
// perform any userland actions with the data
|
|
|
|
|
FireAnalogDataReceived(new AnalogDataReceivedEvent(peerId, srcId, dstId, callType, audioFrameType, frameType, rtpHeader.Sequence, streamId, message));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Unknown protocol opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}");
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Unknown protocol opcode {fneHeader.Function.ToString("X2")} / {fneHeader.SubFunction.ToString("X2")} -- {FneUtils.HexDump(message, 0)}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Constants.NET_FUNC_MASTER:
|
|
|
|
|
case Constants.NET_FUNC_MASTER: // Master
|
|
|
|
|
{
|
|
|
|
|
/* stub */
|
|
|
|
|
if (this.peerId == peerId)
|
|
|
|
|
{
|
|
|
|
|
// process incoming message subfunction opcodes
|
|
|
|
|
switch (fneHeader.SubFunction)
|
|
|
|
|
{
|
|
|
|
|
case Constants.NET_MASTER_SUBFUNC_WL_RID: // Whitelisted Radio IDs
|
|
|
|
|
case Constants.NET_MASTER_SUBFUNC_BL_RID: // Blacklisted Radio IDs
|
|
|
|
|
// ignore these
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Constants.NET_MASTER_SUBFUNC_ACTIVE_TGS: // Talkgroup Active IDs
|
|
|
|
|
{
|
|
|
|
|
uint len = FneUtils.ToUInt32(message, 6);
|
|
|
|
|
int offs = 11;
|
|
|
|
|
for (int i = 0; i < len; i++)
|
|
|
|
|
{
|
|
|
|
|
uint id = FneUtils.Bytes3ToUInt32(message, offs);
|
|
|
|
|
byte slot = (byte)(message[offs + 3] & 0x03);
|
|
|
|
|
bool affiliated = (message[offs + 3] & 0x40) == 0x40;
|
|
|
|
|
bool nonPreferred = (message[offs + 3] & 0x80) == 0x80;
|
|
|
|
|
|
|
|
|
|
TalkgroupEntry entry = new TalkgroupEntry()
|
|
|
|
|
{
|
|
|
|
|
ID = id,
|
|
|
|
|
Slot = slot,
|
|
|
|
|
Affiliated = affiliated,
|
|
|
|
|
NonPreferred = nonPreferred,
|
|
|
|
|
Invalid = false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int idx = announcedTGs.FindIndex(x => x.ID == id && x.Slot == slot);
|
|
|
|
|
if (idx != -1)
|
|
|
|
|
announcedTGs[idx] = entry;
|
|
|
|
|
else
|
|
|
|
|
announcedTGs.Add(entry);
|
|
|
|
|
|
|
|
|
|
offs += 5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log(LogLevel.INFO, $"Activated {len} TGs; loaded {announcedTGs.Count} entries into talkgroup table");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Constants.NET_MASTER_SUBFUNC_DEACTIVE_TGS: // Talkgroup Deactivated IDs
|
|
|
|
|
{
|
|
|
|
|
uint len = FneUtils.ToUInt32(message, 6);
|
|
|
|
|
int offs = 11;
|
|
|
|
|
for (int i = 0; i < len; i++)
|
|
|
|
|
{
|
|
|
|
|
uint id = FneUtils.Bytes3ToUInt32(message, offs);
|
|
|
|
|
byte slot = message[offs + 3];
|
|
|
|
|
|
|
|
|
|
int idx = announcedTGs.FindIndex(x => x.ID == id && x.Slot == slot);
|
|
|
|
|
if (idx != -1)
|
|
|
|
|
announcedTGs[idx].Invalid = true;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
TalkgroupEntry entry = new TalkgroupEntry()
|
|
|
|
|
{
|
|
|
|
|
ID = id,
|
|
|
|
|
Slot = slot,
|
|
|
|
|
Affiliated = false,
|
|
|
|
|
NonPreferred = false,
|
|
|
|
|
Invalid = true
|
|
|
|
|
};
|
|
|
|
|
announcedTGs.Add(entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
offs += 5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log(LogLevel.INFO, $"Deactivated {len} TGs; loaded {announcedTGs.Count} entries into talkgroup table");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Constants.NET_MASTER_SUBFUNC_HA_PARAMS: // HA Parameters
|
|
|
|
|
{
|
|
|
|
|
haIPs.Clear();
|
|
|
|
|
currentHAIP = 0;
|
|
|
|
|
maxRetryCount = Constants.MAX_RETRY_HA_RECONNECT;
|
|
|
|
|
|
|
|
|
|
// always add the configured address to the HA IP list
|
|
|
|
|
haIPs.Add(new PeerHAIPEntry(masterEndpoint.Address.ToString(), masterEndpoint.Port));
|
|
|
|
|
|
|
|
|
|
uint len = FneUtils.ToUInt32(message, 6);
|
|
|
|
|
if (len > 0)
|
|
|
|
|
len /= Constants.HAParamsEntryLen;
|
|
|
|
|
|
|
|
|
|
int offs = 10;
|
|
|
|
|
for (int i = 0; i < len; i++, offs += (int)Constants.HAParamsEntryLen)
|
|
|
|
|
{
|
|
|
|
|
uint ipAddr = FneUtils.ToUInt32(message, offs + 4);
|
|
|
|
|
ushort port = FneUtils.ToUInt16(message, offs + 8);
|
|
|
|
|
|
|
|
|
|
IPAddress address = new IPAddress(ipAddr);
|
|
|
|
|
haIPs.Add(new PeerHAIPEntry(address.ToString(), port));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (haIPs.Count > 1)
|
|
|
|
|
{
|
|
|
|
|
currentHAIP = 1;
|
|
|
|
|
Log(LogLevel.INFO, $"Loaded {haIPs.Count} HA IPs from master");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Unknown master opcode {fneHeader.Function.ToString("X2")} / {fneHeader.SubFunction.ToString("X2")} -- {FneUtils.HexDump(message, 0)}");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Constants.NET_FUNC_INCALL_CTRL: // In-Call Control
|
|
|
|
|
{
|
|
|
|
|
if (this.peerId == peerId)
|
|
|
|
|
{
|
|
|
|
|
// process incoming message subfunction opcodes
|
|
|
|
|
switch (fneHeader.SubFunction)
|
|
|
|
|
{
|
|
|
|
|
case Constants.NET_PROTOCOL_SUBFUNC_DMR: // DMR In-Call Control
|
|
|
|
|
{
|
|
|
|
|
byte command = message[10];
|
|
|
|
|
uint dstId = FneUtils.Bytes3ToUInt32(message, 11);
|
|
|
|
|
byte slot = message[14];
|
|
|
|
|
|
|
|
|
|
// fire off DMR in-call callback if we have one
|
|
|
|
|
FireDMRInCallControl(new DMRInCallControlEvent(peerId, dstId, slot, command));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Constants.NET_PROTOCOL_SUBFUNC_P25: // P25 In-Call Control
|
|
|
|
|
{
|
|
|
|
|
byte command = message[10];
|
|
|
|
|
uint dstId = FneUtils.Bytes3ToUInt32(message, 11);
|
|
|
|
|
|
|
|
|
|
// fire off P25 in-call callback if we have one
|
|
|
|
|
FireP25InCallControl(new P25InCallControlEvent(peerId, dstId, command));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Constants.NET_PROTOCOL_SUBFUNC_NXDN: // NXDN In-Call Control
|
|
|
|
|
{
|
|
|
|
|
byte command = message[10];
|
|
|
|
|
uint dstId = FneUtils.Bytes3ToUInt32(message, 11);
|
|
|
|
|
|
|
|
|
|
// fire off NXDN in-call callback if we have one
|
|
|
|
|
FireNXDNInCallControl(new NXDNInCallControlEvent(peerId, dstId, command));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Constants.NET_PROTOCOL_SUBFUNC_ANALOG: // Analog In-Call Control
|
|
|
|
|
{
|
|
|
|
|
byte command = message[10];
|
|
|
|
|
uint dstId = FneUtils.Bytes3ToUInt32(message, 11);
|
|
|
|
|
|
|
|
|
|
// fire off analog in-call callback if we have one
|
|
|
|
|
FireAnalogInCallControl(new AnalogInCallControlEvent(peerId, dstId, command));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Unknown incall control opcode {fneHeader.Function.ToString("X2")} / {fneHeader.SubFunction.ToString("X2")} -- {FneUtils.HexDump(message, 0)}");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
@ -665,6 +938,7 @@ namespace fnecore
|
|
|
|
|
// peer types
|
|
|
|
|
jsonWriter.WriteBoolean("externalPeer", info.Details.ExternalPeer);
|
|
|
|
|
jsonWriter.WriteBoolean("conventionalPeer", info.Details.ConventionalPeer);
|
|
|
|
|
jsonWriter.WriteBoolean("sysView", info.Details.SysView);
|
|
|
|
|
|
|
|
|
|
// system info
|
|
|
|
|
{
|
|
|
|
|
@ -735,6 +1009,7 @@ namespace fnecore
|
|
|
|
|
Log(LogLevel.INFO, $"({systemName}) PEER {this.peerId} connection to MASTER completed");
|
|
|
|
|
|
|
|
|
|
this.streamId = 0;
|
|
|
|
|
retryCount = 0; // reset retry count
|
|
|
|
|
|
|
|
|
|
// userland actions
|
|
|
|
|
FirePeerConnected(new PeerConnectedEvent(peerId, info));
|
|
|
|
|
@ -791,7 +1066,14 @@ namespace fnecore
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Unknown opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}");
|
|
|
|
|
// are we handling anything else with a user handler?
|
|
|
|
|
if (UserPacketHandler != null)
|
|
|
|
|
{
|
|
|
|
|
UserPacketHandler(frame, peerId, streamId, rtpHeader, fneHeader, message);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Unknown opcode {fneHeader.Function.ToString("X2")} / {fneHeader.SubFunction.ToString("X2")} -- {FneUtils.HexDump(message, 0)}");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -799,6 +1081,7 @@ namespace fnecore
|
|
|
|
|
catch (InvalidOperationException)
|
|
|
|
|
{
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting...");
|
|
|
|
|
RotateMasterEndpont();
|
|
|
|
|
|
|
|
|
|
// reset states
|
|
|
|
|
PingsSent = 0;
|
|
|
|
|
@ -817,6 +1100,7 @@ namespace fnecore
|
|
|
|
|
case SocketError.ConnectionAborted:
|
|
|
|
|
case SocketError.ConnectionRefused:
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting...");
|
|
|
|
|
RotateMasterEndpont();
|
|
|
|
|
|
|
|
|
|
// reset states
|
|
|
|
|
PingsSent = 0;
|
|
|
|
|
@ -871,6 +1155,7 @@ namespace fnecore
|
|
|
|
|
if (PingsSent > (PingsAcked + MAX_MISSED_PEER_PINGS))
|
|
|
|
|
{
|
|
|
|
|
Log(LogLevel.WARNING, $"({systemName} Peer connection lost to {masterEndpoint}; reconnecting...");
|
|
|
|
|
RotateMasterEndpont();
|
|
|
|
|
|
|
|
|
|
// reset states
|
|
|
|
|
PingsSent = 0;
|
|
|
|
|
@ -893,6 +1178,7 @@ namespace fnecore
|
|
|
|
|
catch (InvalidOperationException)
|
|
|
|
|
{
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting...");
|
|
|
|
|
RotateMasterEndpont();
|
|
|
|
|
|
|
|
|
|
// reset states
|
|
|
|
|
PingsSent = 0;
|
|
|
|
|
@ -911,6 +1197,7 @@ namespace fnecore
|
|
|
|
|
case SocketError.ConnectionAborted:
|
|
|
|
|
case SocketError.ConnectionRefused:
|
|
|
|
|
Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting...");
|
|
|
|
|
RotateMasterEndpont();
|
|
|
|
|
|
|
|
|
|
// reset states
|
|
|
|
|
PingsSent = 0;
|
|
|
|
|
|