Merge R04K34 dev into fnecore master (#2)

* add support for upcoming DVM R05A features;

* update README.md;
master
Bryan Biedenkapp 1 month ago committed by GitHub
parent 054954fad3
commit 5ef56a698a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,36 @@
// 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) 2025 Bryan Biedenkapp, N2PLL
*
*/
using System;
namespace fnecore.Analog
{
/// <summary>
/// Audio Frame Type(s)
/// </summary>
public enum AudioFrameType : byte
{
/// <summary>
/// Voice Start Frame
/// </summary>
VOICE_START = 0x00,
/// <summary>
/// Voice
/// </summary>
VOICE = 0x01,
/// <summary>
/// Voice End Frame / Call Terminator
/// </summary>
TERMINATOR = 0x02
} // public enum AudioFrameType : byte
} // namespace fnecore.Analog

@ -7,7 +7,7 @@
* @package DVM / Fixed Network Equipment Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2022 Bryan Biedenkapp, N2PLL
* Copyright (C) 2022,2025 Bryan Biedenkapp, N2PLL
*
*/
@ -196,6 +196,19 @@ namespace fnecore
public const byte DVMRtpPayloadType = 0x56;
public const byte DVMFrameStart = 0xFE;
public const uint DMRPacketLength = 55U; // 20 byte header + DMR_FRAME_LENGTH_BYTES + 2 byte trailer
public const uint P25LDU1PacketLength = 193U; // 24 byte header + DFSI data + 1 byte frame type + 12 byte enc sync
public const uint P25LDU2PacketLength = 181U; // 24 byte header + DFSI data + 1 byte frame type
public const uint P25TSDUPacketLength = 69U; // 24 byte header + TSDU data
public const uint P25TDULCPacketLength = 78U; // 24 byte header + TDULC data
public const uint NXDNPacketLength = 70U; // 20 byte header + NXDN_FRAME_LENGTH_BYTES + 2 byte trailer
public const uint AnalogPacketLength = 324U; // 20 byte header + AUDIO_SAMPLES_LENGTH_BYTES + 4 byte trailer
public const uint HAParamsEntryLen = 20;
public const int MAX_RETRY_BEFORE_RECONNECT = 4;
public const int MAX_RETRY_HA_RECONNECT = 2;
/*
** Protocol Functions and Sub-Functions
*/
@ -205,12 +218,14 @@ namespace fnecore
public const byte NET_PROTOCOL_SUBFUNC_DMR = 0x00; // DMR
public const byte NET_PROTOCOL_SUBFUNC_P25 = 0x01; // P25
public const byte NET_PROTOCOL_SUBFUNC_NXDN = 0x02; // NXDN
public const byte NET_PROTOCOL_SUBFUNC_ANALOG = 0x03; // Analog
public const byte NET_FUNC_MASTER = 0x01; // Network Master Function
public const byte NET_MASTER_SUBFUNC_WL_RID = 0x00; // Whitelist RIDs
public const byte NET_MASTER_SUBFUNC_BL_RID = 0x01; // Blacklist RIDs
public const byte NET_MASTER_SUBFUNC_ACTIVE_TGS = 0x02; // Active TGIDs
public const byte NET_MASTER_SUBFUNC_DEACTIVE_TGS = 0x03; // Deactive TGIDs
public const byte NET_MASTER_SUBFUNC_HA_PARAMS = 0xA3; // HA Parameters
public const byte NET_FUNC_RPTL = 0x60; // Repeater Login
public const byte NET_FUNC_RPTK = 0x61; // Repeater Authorisation
@ -223,6 +238,7 @@ namespace fnecore
public const byte NET_FUNC_PONG = 0x75; // Pong
public const byte NET_FUNC_GRANT = 0x7A; // Grant Request
public const byte NET_FUNC_INCALL_CTRL = 0x7B; // In-CAll Control
public const byte NET_FUNC_KEY_REQ = 0x7C; // Encryption Key Request
public const byte NET_FUNC_KEY_RSP = 0x7D; // Encryption Key Response
@ -240,6 +256,9 @@ namespace fnecore
public const byte NET_ANNC_SUBFUNC_GRP_UNAFFIL = 0x03; // Announce Group Affiliation Removal
public const byte NET_ANNC_SUBFUNC_AFFILS = 0x90; // Update All Affiliations
public const byte NET_ICC_BUSY_DENY = 0x00; // In-Call Busy Deny
public const byte NET_ICC_REJECT_TRAFFIC = 0x01; // In-Call Reject Active Traffic
/*
** Protocol Tags (as strings)
*/

@ -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
*
*/
@ -18,6 +18,7 @@ using System.Security.Cryptography;
using fnecore.DMR;
using fnecore.P25;
using fnecore.NXDN;
using fnecore.Analog;
using fnecore.EDAC;
using fnecore.P25.KMM;
@ -190,6 +191,80 @@ namespace fnecore
}
} // public class PeerInformation
/// <summary>
/// Represents a talkgroup as announced from the FNE master.
/// </summary>
public class TalkgroupEntry
{
/// <summary>
/// Talkgroup ID.
/// </summary>
public uint ID;
/// <summary>
/// Slot Number.
/// </summary>
public byte Slot;
/// <summary>
///
/// </summary>
public bool Affiliated;
/// <summary>
///
/// </summary>
public bool NonPreferred;
/// <summary>
///
/// </summary>
public bool Invalid;
} // public class TalkgroupEntry
/// <summary>
/// Represents high availiability IP address data.
/// </summary>
public class PeerHAIPEntry
{
/// <summary>
/// IP Address
/// </summary>
public string Address;
/// <summary>
/// Port Number
/// </summary>
public int Port;
/// <summary>
///
/// </summary>
public IPEndPoint EndPoint;
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="PeerHAIPEntry"/> class.
/// </summary>
public PeerHAIPEntry()
{
Address = "127.0.0.1";
Port = 62031; // this should be a constant not a magic number -- but I digress
EndPoint = new IPEndPoint(IPAddress.Parse(Address), Port);
}
/// <summary>
/// Initializes a new instance of the <see cref="PeerHAIPEntry"/> class.
/// </summary>
/// <param name="address">IP Address</param>
/// <param name="port">Port number</param>
public PeerHAIPEntry(string address, int port)
{
Address = address;
Port = port;
EndPoint = new IPEndPoint(IPAddress.Parse(Address), Port);
}
} // public class PeerHAIPEntry
/// <summary>
/// Callback used to validate incoming DMR data.
/// </summary>
@ -296,6 +371,54 @@ namespace fnecore
Buffer.BlockCopy(data, 0, Data, 0, data.Length);
}
} // public class DMRDataReceivedEvent : EventArgs
/// <summary>
/// Event used to process incoming DMR In-Call control data.
/// </summary>
public class DMRInCallControlEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// Destination Address
/// </summary>
public uint DstId { get; }
/// <summary>
/// Slot Number
/// </summary>
public byte Slot { get; }
/// <summary>
/// In-Call Control Command
/// </summary>
public byte Command { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="DMRInCallControlEvent"/> class.
/// </summary>
private DMRInCallControlEvent()
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="DMRInCallControlEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="dstId">Destination Address</param>
/// <param name="slot">Slot Number</param>
/// <param name="command">In-Call Command</param>
public DMRInCallControlEvent(uint peerId, uint dstId, byte slot, byte command) : base()
{
this.PeerId = peerId;
this.DstId = dstId;
this.Slot = slot;
this.Command = command;
}
} // public class DMRDataReceivedEvent : EventArgs
/// <summary>
/// Callback used to validate incoming P25 data.
@ -390,6 +513,48 @@ namespace fnecore
Buffer.BlockCopy(data, 0, Data, 0, data.Length);
}
} // public class P25DataReceivedEvent : EventArgs
/// <summary>
/// Event used to process incoming P25 In-Call control data.
/// </summary>
public class P25InCallControlEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// Destination Address
/// </summary>
public uint DstId { get; }
/// <summary>
/// In-Call Control Command
/// </summary>
public byte Command { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="P25InCallControlEvent"/> class.
/// </summary>
private P25InCallControlEvent()
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="P25InCallControlEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="dstId">Destination Address</param>
/// <param name="command">In-Call Command</param>
public P25InCallControlEvent(uint peerId, uint dstId, byte command) : base()
{
this.PeerId = peerId;
this.DstId = dstId;
this.Command = command;
}
} // public class P25InCallControlEvent : EventArgs
/// <summary>
/// Callback used to validate incoming NXDN data.
@ -484,6 +649,184 @@ namespace fnecore
Buffer.BlockCopy(data, 0, Data, 0, data.Length);
}
} // public class NXDNDataReceivedEvent : EventArgs
/// <summary>
/// Event used to process incoming NXDN In-Call control data.
/// </summary>
public class NXDNInCallControlEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// Destination Address
/// </summary>
public uint DstId { get; }
/// <summary>
/// In-Call Control Command
/// </summary>
public byte Command { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="NXDNInCallControlEvent"/> class.
/// </summary>
private NXDNInCallControlEvent()
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="NXDNInCallControlEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="dstId">Destination Address</param>
/// <param name="command">In-Call Command</param>
public NXDNInCallControlEvent(uint peerId, uint dstId, byte command) : base()
{
this.PeerId = peerId;
this.DstId = dstId;
this.Command = command;
}
} // public class NXDNInCallControlEvent : EventArgs
/// <summary>
/// Callback used to validate incoming analog data.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="callType">Call Type (Group or Private)</param>
/// <param name="audioFrameType">Analog Audio Frame Type</param>
/// <param name="frameType">Frame Type</param>
/// <param name="streamId">Stream ID</param>
/// <param name="message">Raw message data</param>
/// <returns>True, if data stream is valid, otherwise false.</returns>
public delegate bool AnalogDataValidate(uint peerId, uint srcId, uint dstId, CallType callType, AudioFrameType audioFrameType, FrameType frameType, uint streamId, byte[] message);
/// <summary>
/// Event used to process incoming analog data.
/// </summary>
public class AnalogDataReceivedEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// Source Address
/// </summary>
public uint SrcId { get; }
/// <summary>
/// Destination Address
/// </summary>
public uint DstId { get; }
/// <summary>
/// Call Type (Group or Private)
/// </summary>
public CallType CallType { get; }
/// <summary>
/// Audio Frame Type
/// </summary>
public AudioFrameType AudioFrameType { get; }
/// <summary>
/// Frame Type
/// </summary>
public FrameType FrameType { get; }
/// <summary>
/// RTP Packet Sequence
/// </summary>
public ushort PacketSequence { get; }
/// <summary>
/// Stream ID
/// </summary>
public uint StreamId { get; }
/// <summary>
/// Raw message data
/// </summary>
public byte[] Data { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="AnalogDataReceivedEvent"/> class.
/// </summary>
private AnalogDataReceivedEvent()
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="AnalogDataReceivedEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="srcId">Source Address</param>
/// <param name="dstId">Destination Address</param>
/// <param name="callType">Call Type (Group or Private)</param>
/// <param name="audioFrameType">Audio Message Type</param>
/// <param name="frameType">Frame Type</param>
/// <param name="pktSeq">RTP Packet Sequence</param>
/// <param name="streamId">Stream ID</param>
/// <param name="data">Raw message data</param>
public AnalogDataReceivedEvent(uint peerId, uint srcId, uint dstId, CallType callType, AudioFrameType audioFrameType, FrameType frameType, ushort pktSeq, uint streamId, byte[] data) : base()
{
this.PeerId = peerId;
this.SrcId = srcId;
this.DstId = dstId;
this.CallType = callType;
this.AudioFrameType = audioFrameType;
this.FrameType = frameType;
this.PacketSequence = pktSeq;
this.StreamId = streamId;
this.Data = new byte[data.Length];
Buffer.BlockCopy(data, 0, Data, 0, data.Length);
}
} // public class AnalogDataReceivedEvent : EventArgs
/// <summary>
/// Event used to process incoming analog In-Call control data.
/// </summary>
public class AnalogInCallControlEvent : EventArgs
{
/// <summary>
/// Peer ID
/// </summary>
public uint PeerId { get; }
/// <summary>
/// Destination Address
/// </summary>
public uint DstId { get; }
/// <summary>
/// In-Call Control Command
/// </summary>
public byte Command { get; }
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="AnalogInCallControlEvent"/> class.
/// </summary>
private AnalogInCallControlEvent()
{
/* stub */
}
/// <summary>
/// Initializes a new instance of the <see cref="AnalogInCallControlEvent"/> class.
/// </summary>
/// <param name="peerId">Peer ID</param>
/// <param name="dstId">Destination Address</param>
/// <param name="command">In-Call Command</param>
public AnalogInCallControlEvent(uint peerId, uint dstId, byte command) : base()
{
this.PeerId = peerId;
this.DstId = dstId;
this.Command = command;
}
} // public class AnalogInCallControlEvent : EventArgs
/// <summary>
/// Callback used to process whether or not a peer is being ignored for traffic.
@ -629,6 +972,10 @@ namespace fnecore
/// Event action that handles processing a DMR call stream.
/// </summary>
public event EventHandler<DMRDataReceivedEvent> DMRDataReceived;
/// <summary>
/// Event action that handles processing a DMR In-Call control request.
/// </summary>
public event EventHandler<DMRInCallControlEvent> DMRInCallControl;
/// <summary>
/// Callback action that handles validating a P25 call stream.
@ -642,6 +989,10 @@ namespace fnecore
/// Event action that handles processing a P25 call stream.
/// </summary>
public event EventHandler<P25DataReceivedEvent> P25DataReceived;
/// <summary>
/// Event action that handles processing a P25 In-Call control request.
/// </summary>
public event EventHandler<P25InCallControlEvent> P25InCallControl;
/// <summary>
/// Callback action that handles validating a NXDN call stream.
@ -651,6 +1002,23 @@ namespace fnecore
/// Event action that handles processing a NXDN call stream.
/// </summary>
public event EventHandler<NXDNDataReceivedEvent> NXDNDataReceived;
/// <summary>
/// Event action that handles processing a NXDN In-Call control request.
/// </summary>
public event EventHandler<NXDNInCallControlEvent> NXDNInCallControl;
/// <summary>
/// Callback action that handles validating a analog call stream.
/// </summary>
public AnalogDataValidate AnalogDataValidate = null;
/// <summary>
/// Event action that handles processing a analog call stream.
/// </summary>
public event EventHandler<AnalogDataReceivedEvent> AnalogDataReceived;
/// <summary>
/// Event action that handles processing a analog In-Call control request.
/// </summary>
public event EventHandler<AnalogInCallControlEvent> AnalogInCallControl;
/// <summary>
/// Callback action that handles verifying if a peer is ignored for a call stream.
@ -877,6 +1245,16 @@ namespace fnecore
DMRDataReceived.Invoke(this, e);
}
/// <summary>
/// Helper to fire the DMR In-Call control event.
/// </summary>
/// <param name="e"><see cref=""/> instance</param>
protected void FireDMRInCallControl(DMRInCallControlEvent e)
{
if (DMRInCallControl != null)
DMRInCallControl.Invoke(this, e);
}
/// <summary>
/// Helper to fire the P25 data pre-process event.
/// </summary>
@ -897,6 +1275,16 @@ namespace fnecore
P25DataReceived.Invoke(this, e);
}
/// <summary>
/// Helper to fire the P25 In-Call control event.
/// </summary>
/// <param name="e"><see cref=""/> instance</param>
protected void FireP25InCallControl(P25InCallControlEvent e)
{
if (P25InCallControl != null)
P25InCallControl.Invoke(this, e);
}
/// <summary>
/// Helper to fire the NXDN data received event.
/// </summary>
@ -907,6 +1295,36 @@ namespace fnecore
NXDNDataReceived.Invoke(this, e);
}
/// <summary>
/// Helper to fire the NXDN In-Call control event.
/// </summary>
/// <param name="e"><see cref=""/> instance</param>
protected void FireNXDNInCallControl(NXDNInCallControlEvent e)
{
if (NXDNInCallControl != null)
NXDNInCallControl.Invoke(this, e);
}
/// <summary>
/// Helper to fire the analog data received event.
/// </summary>
/// <param name="e"><see cref="AnalogDataReceivedEvent"/> instance</param>
protected void FireAnalogDataReceived(AnalogDataReceivedEvent e)
{
if (AnalogDataReceived != null)
AnalogDataReceived.Invoke(this, e);
}
/// <summary>
/// Helper to fire the analog In-Call control event.
/// </summary>
/// <param name="e"><see cref=""/> instance</param>
protected void FireAnalogInCallControl(AnalogInCallControlEvent e)
{
if (AnalogInCallControl != null)
AnalogInCallControl.Invoke(this, e);
}
/// <summary>
/// Helper to fire the peer connected event.
/// </summary>

@ -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
{
@ -62,6 +62,13 @@ 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;
/*
** Properties
*/
@ -84,6 +91,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>
@ -402,6 +414,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 +497,7 @@ namespace fnecore
{
case Constants.NET_FUNC_PROTOCOL:
{
if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_DMR) // Encapsulated DMR data frame
if (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_DMR) // Encapsulated DMR data frame
{
if (peerId != this.peerId)
{
@ -472,6 +508,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);
@ -493,7 +531,7 @@ namespace fnecore
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,6 +542,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
uint srcId = FneUtils.Bytes3ToUInt32(message, 5);
uint dstId = FneUtils.Bytes3ToUInt32(message, 8);
CallType callType = (message[4] == P25Defines.LC_PRIVATE) ? CallType.PRIVATE : CallType.GROUP;
@ -516,7 +556,7 @@ namespace fnecore
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 +567,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);
@ -541,6 +583,31 @@ namespace fnecore
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
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)}");
@ -548,9 +615,166 @@ namespace fnecore
}
break;
case Constants.NET_FUNC_MASTER:
case Constants.NET_FUNC_MASTER: // Master
{
if (this.peerId == peerId)
{
// process incoming message subfunction opcodes
switch (fneHeader.SubFunction)
{
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 protocol opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}");
break;
}
}
}
break;
case Constants.NET_FUNC_INCALL_CTRL: // In-Call Control
{
/* stub */
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 {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}");
break;
}
}
}
break;
@ -735,6 +959,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));
@ -799,6 +1024,7 @@ namespace fnecore
catch (InvalidOperationException)
{
Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting...");
RotateMasterEndpont();
// reset states
PingsSent = 0;
@ -817,6 +1043,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 +1098,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 +1121,7 @@ namespace fnecore
catch (InvalidOperationException)
{
Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting...");
RotateMasterEndpont();
// reset states
PingsSent = 0;
@ -911,6 +1140,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;

@ -1,6 +1,6 @@
# Digital Voice Modem Fixed Network Equipment
This is a library project that implements the basic communications layer for implementing DVM FNE clients (peers) and servers (masters).
This is a library project that implements the basic communications layer for implementing DVM FNE clients (peers).
## License

Loading…
Cancel
Save

Powered by TurnKey Linux.