Compare commits

..

No commits in common. 'master' and '4.32j_maint' have entirely different histories.

@ -1,36 +0,0 @@
// 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,2025 Bryan Biedenkapp, N2PLL
* Copyright (C) 2022 Bryan Biedenkapp, N2PLL
*
*/
@ -196,19 +196,6 @@ 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
*/
@ -218,14 +205,12 @@ 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
@ -238,7 +223,6 @@ 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
@ -248,7 +232,6 @@ namespace fnecore
public const byte NET_FUNC_TRANSFER = 0x90; // Network Transfer Function
public const byte NET_TRANSFER_SUBFUNC_ACTIVITY = 0x01; // Activity Log Transfer
public const byte NET_TRANSFER_SUBFUNC_DIAG = 0x02; // Diagnostic Log Transfer
public const byte NET_TRANSFER_SUBFUNC_STATUS = 0x03; // Status Transfer
public const byte NET_FUNC_ANNOUNCE = 0x91; // Network Announce Function
public const byte NET_ANNC_SUBFUNC_GRP_AFFIL = 0x00; // Announce Group Affiliation
@ -257,9 +240,6 @@ 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-2025 Bryan Biedenkapp, N2PLL
* Copyright (C) 2022-2023 Bryan Biedenkapp, N2PLL
*
*/
@ -18,7 +18,6 @@ using System.Security.Cryptography;
using fnecore.DMR;
using fnecore.P25;
using fnecore.NXDN;
using fnecore.Analog;
using fnecore.EDAC;
using fnecore.P25.KMM;
@ -50,10 +49,6 @@ namespace fnecore
/// Conventional Peer
/// </summary>
public bool ConventionalPeer;
/// <summary>
/// System View
/// </summary>
public bool SysView;
/// <summary>
/// Software Identifier
@ -195,80 +190,6 @@ 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>
@ -375,54 +296,6 @@ 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.
@ -517,48 +390,6 @@ 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.
@ -653,184 +484,6 @@ 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.
@ -976,10 +629,6 @@ 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.
@ -993,10 +642,6 @@ 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.
@ -1006,23 +651,6 @@ 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.
@ -1249,16 +877,6 @@ 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>
@ -1279,16 +897,6 @@ 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>
@ -1299,36 +907,6 @@ 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-2025 Bryan Biedenkapp, N2PLL
* Copyright (C) 2022-2023 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,18 +39,6 @@ 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".
@ -74,15 +62,6 @@ 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
*/
@ -105,11 +84,6 @@ 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>
@ -128,16 +102,6 @@ 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
*/
@ -146,10 +110,6 @@ 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
@ -162,9 +122,8 @@ 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, bool trafficLogging = false) : this(systemName, peerId, new IPEndPoint(IPAddress.Parse(address), port), PresharedKey)
public FnePeer(string systemName, uint peerId, string address, int port, string PresharedKey = null) : this(systemName, peerId, new IPEndPoint(IPAddress.Parse(address), port), PresharedKey)
{
this.trafficLogging = trafficLogging;
/* stub */
}
@ -174,7 +133,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, bool trafficLogging = false) : base(systemName, peerId)
public FnePeer(string systemName, uint peerId, IPEndPoint endpoint, string PresharedKey = null) : base(systemName, peerId)
{
masterEndpoint = endpoint;
client = new UdpReceiver();
@ -186,11 +145,7 @@ namespace fnecore
info.PeerID = peerId;
info.State = ConnectionState.WAITING_LOGIN;
UserProtocolHandler = false;
PingsAcked = 0;
this.trafficLogging = trafficLogging;
}
/// <summary>
@ -447,30 +402,6 @@ 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>
@ -530,14 +461,7 @@ namespace fnecore
{
case Constants.NET_FUNC_PROTOCOL:
{
// 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 (fneHeader.SubFunction == Constants.NET_PROTOCOL_SUBFUNC_DMR) // Encapsulated DMR data frame
{
if (peerId != this.peerId)
{
@ -548,8 +472,6 @@ 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);
@ -565,14 +487,13 @@ namespace fnecore
byte n = (byte)(bits & 0xF);
#if DEBUG
if (trafficLogging)
Log(LogLevel.DEBUG, $"{systemName} DMRD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} TS {slot} [STREAM ID {streamId}]");
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)
{
@ -583,22 +504,19 @@ 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
if (trafficLogging)
Log(LogLevel.DEBUG, $"{systemName} P25D: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}]");
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)
{
@ -609,8 +527,6 @@ 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);
@ -619,211 +535,22 @@ 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
if (trafficLogging)
Log(LogLevel.DEBUG, $"{systemName} NXDD: SRC_PEER {peerId} SRC_ID {srcId} DST_ID {dstId} [STREAM ID {streamId}]");
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 {fneHeader.Function.ToString("X2")} / {fneHeader.SubFunction.ToString("X2")} -- {FneUtils.HexDump(message, 0)}");
Log(LogLevel.ERROR, $"({systemName}) Unknown protocol opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}");
}
}
break;
case Constants.NET_FUNC_MASTER: // Master
case Constants.NET_FUNC_MASTER:
{
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;
}
}
/* stub */
}
break;
@ -938,7 +665,6 @@ namespace fnecore
// peer types
jsonWriter.WriteBoolean("externalPeer", info.Details.ExternalPeer);
jsonWriter.WriteBoolean("conventionalPeer", info.Details.ConventionalPeer);
jsonWriter.WriteBoolean("sysView", info.Details.SysView);
// system info
{
@ -1009,7 +735,6 @@ 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));
@ -1066,14 +791,7 @@ namespace fnecore
break;
default:
// 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)}");
Log(LogLevel.ERROR, $"({systemName}) Unknown opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}");
break;
}
}
@ -1081,7 +799,6 @@ namespace fnecore
catch (InvalidOperationException)
{
Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting...");
RotateMasterEndpont();
// reset states
PingsSent = 0;
@ -1100,7 +817,6 @@ 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;
@ -1155,7 +871,6 @@ namespace fnecore
if (PingsSent > (PingsAcked + MAX_MISSED_PEER_PINGS))
{
Log(LogLevel.WARNING, $"({systemName} Peer connection lost to {masterEndpoint}; reconnecting...");
RotateMasterEndpont();
// reset states
PingsSent = 0;
@ -1178,7 +893,6 @@ namespace fnecore
catch (InvalidOperationException)
{
Log(LogLevel.ERROR, $"({systemName}) Not connected or lost connection to {masterEndpoint}; reconnecting...");
RotateMasterEndpont();
// reset states
PingsSent = 0;
@ -1197,7 +911,6 @@ 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;

@ -7,7 +7,7 @@
* @package DVM / Fixed Network Equipment Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2024-2026 Bryan Biedenkapp, N2PLL
* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL
*
*/
@ -22,7 +22,6 @@ using fnecore.EDAC;
using fnecore.DMR;
using fnecore.P25;
using fnecore.NXDN;
using fnecore.P25.LC.TSBK;
namespace fnecore
{

@ -154,7 +154,6 @@ namespace fnecore.P25
public const byte P25_FT_DATA_UNIT = 0x00;
public const byte P25_MFG_STANDARD = 0x00;
public const byte P25_MFG_DVM_OCS = 0x9C;
public const byte P25_ALGO_UNENCRYPT = 0x80;
public const byte P25_ALGO_DES = 0x81;

@ -1,81 +0,0 @@
// 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) 2026 Bryan Biedenkapp, N2PLL
*
*/
namespace fnecore.P25.LC.TSBK
{
/// <summary>
/// OSP_DVM_LC_CALL_TERM TSBK
/// </summary>
public class OSP_DVM_LC_CALL_TERM : TSBKBase
{
public byte GrpVchId;
public uint GrpVchNo;
public uint DstId;
public uint SrcId;
/// <summary>
/// Creates an instance of <see cref="OSP_DVM_LC_CALL_TERM"/>
/// </summary>
/// <param name="dstId"></param>
/// <param name="srcId"></param>
public OSP_DVM_LC_CALL_TERM(uint dstId = 0, uint srcId = 0)
{
DstId = dstId;
SrcId = srcId;
Lco = P25Defines.LC_CALL_TERM;
MfId = P25Defines.P25_MFG_DVM_OCS;
}
/// <summary>
/// Decode CALL_ALRT TSBK
/// </summary>
/// <param name="data"></param>
/// <param name="rawTSBK"></param>
/// <returns></returns>
public override bool Decode(byte[] data, bool rawTSBK = true)
{
if (!base.Decode(data, rawTSBK))
return false;
ulong tsbkValue = FneUtils.ToUInt64(Payload, 0);
GrpVchId = (byte)((tsbkValue >> 52) & 0x0F); // Channel ID
GrpVchNo = (uint)((tsbkValue >> 40) & 0xFFF); // Channel Number
DstId = (uint)((tsbkValue >> 24) & 0xFFFF); // Target Radio Address
SrcId = (uint)(tsbkValue & 0xFFFFFF); // Source Radio Address
return true;
}
/// <summary>
/// Encode CALL_ALRT TSBK
/// </summary>
/// <param name="data"></param>
/// <param name="payload"></param>
/// <param name="rawTSBK"></param>
/// <param name="noTrellis"></param>
public override void Encode(ref byte[] data, bool rawTSBK = true, bool noTrellis = true)
{
ulong tsbkValue = 0;
tsbkValue = (tsbkValue << 4) + GrpVchNo; // Channel ID
tsbkValue = (tsbkValue << 12) + GrpVchId; // Channel Number
tsbkValue = (tsbkValue << 16) + DstId; // Target Radio Address
tsbkValue = (tsbkValue << 24) + SrcId; // Source Radio Address
FneUtils.Memset(Payload, 0x00, Payload.Length);
FneUtils.WriteBytes(tsbkValue, ref Payload, 0);
base.Encode(ref data, rawTSBK, noTrellis);
}
} // public class OSP_DVM_LC_CALL_TERM
} // namespace fnecore.P25.LC.TSBK

@ -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).
This is a library project that implements the basic communications layer for implementing DVM FNE clients (peers) and servers (masters).
## License

Loading…
Cancel
Save

Powered by TurnKey Linux.