From 110af6975c0370b404a4b2140ae6a30615b12a13 Mon Sep 17 00:00:00 2001
From: W3AXL <29879554+W3AXL@users.noreply.github.com>
Date: Tue, 21 Apr 2026 10:35:07 -0500
Subject: [PATCH 1/4] crypto refactor, now half-broken and sort of working but
still cuts out and misses calls
---
rc2-core | 2 +-
rc2-dvm/Configuration.cs | 8 ++
rc2-dvm/FneSystemBase.P25.cs | 19 ---
rc2-dvm/Radio.DVM.cs | 34 +++--
rc2-dvm/VirtualChannel.P25.cs | 236 +++++++++++++++++++---------------
rc2-dvm/VirtualChannel.cs | 181 ++++++++++++++++++++++----
rc2-dvm/config.example.yml | 5 +
rc2-dvm/rc2-dvm.csproj | 2 +-
8 files changed, 327 insertions(+), 160 deletions(-)
diff --git a/rc2-core b/rc2-core
index 7040025..3c670f1 160000
--- a/rc2-core
+++ b/rc2-core
@@ -1 +1 @@
-Subproject commit 7040025d525549c1a2e6d0fe7bb08e4835c99c0b
+Subproject commit 3c670f1270a8cc63013a12dd99c56118af3069a5
diff --git a/rc2-dvm/Configuration.cs b/rc2-dvm/Configuration.cs
index 615d09e..206c716 100644
--- a/rc2-dvm/Configuration.cs
+++ b/rc2-dvm/Configuration.cs
@@ -135,6 +135,10 @@ public class TalkgroupConfigObject
/// Whether or not this talkgroup is included in the scanlist
///
public bool Scan = true;
+ ///
+ /// Whether the talkgroup is nuisance deleted (should not be configured in the yml, internal use only)
+ ///
+ public bool NuisanceDeleted = false;
}
///
@@ -253,6 +257,10 @@ public class NetworkConfigObject
/// Whether to enable additional debug messages
///
public bool Debug = false;
+ ///
+ /// List of allowed subnets/networks for incoming WebRTC connections
+ ///
+ public List AllowedNetworks = new();
}
public class EncryptionConfigObject
diff --git a/rc2-dvm/FneSystemBase.P25.cs b/rc2-dvm/FneSystemBase.P25.cs
index faccfe3..92ab43c 100644
--- a/rc2-dvm/FneSystemBase.P25.cs
+++ b/rc2-dvm/FneSystemBase.P25.cs
@@ -495,25 +495,6 @@ protected override void P25DataReceived(object sender, P25DataReceivedEvent e)
foreach (VirtualChannel channel in RC2DVM.VirtualChannels)
{
channel.P25DataReceived(e, pktTime);
- /**
- // Don't send to channels that are transmitting
- if (channel.IsTransmitting()) {
- Log.Logger.Debug("({0:l}) Not sending data from P25 TG {1} to channel {2:l}, channel is currently transmitting", RC2DVM.fneSystem.SystemName, e.DstId, channel.Config.Name);
- continue;
- }
- // Send data to any channel on the TG
- if (channel.IsTalkgroupSelected(VocoderMode.P25, e.DstId))
- {
- Log.Logger.Debug("({0:l}) P25 TG {1} {2:l} -> {3:l}", RC2DVM.fneSystem.SystemName, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID), channel.Config.Name);
- channel.P25DataReceived(e, pktTime);
- }
- // Send to any channel with the ATG configured
- else if (channel.Config.AnnouncementGroup == e.DstId)
- {
- Log.Logger.Debug("({0:l}) P25 ATG {1} {2:l} -> {3:l}", RC2DVM.fneSystem.SystemName, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID), channel.Config.Name);
- channel.P25DataReceived(e, pktTime);
- }
- // Send to any channel with the talkgroup in its scanlist*/
}
return;
diff --git a/rc2-dvm/Radio.DVM.cs b/rc2-dvm/Radio.DVM.cs
index 1697fa2..f206bc4 100644
--- a/rc2-dvm/Radio.DVM.cs
+++ b/rc2-dvm/Radio.DVM.cs
@@ -19,6 +19,7 @@ internal class DVMRadio : Radio
SoftkeyName.HOME,
SoftkeyName.SCAN,
SoftkeyName.SEC,
+ SoftkeyName.DEL
};
// Talkgroup list
@@ -39,8 +40,9 @@ internal class DVMRadio : Radio
public DVMRadio(
string name, bool rxOnly,
IPAddress listenAddress, int listenPort,
+ List allowedNetworks,
List talkgroups, VirtualChannel vChannel,
- Action txAudioCallback, int txAudioSampleRate) : base(name, "", rxOnly, listenAddress, listenPort, DVMSoftkeys, null, null, txAudioCallback, txAudioSampleRate)
+ Action txAudioCallback, int txAudioSampleRate) : base(name, "", rxOnly, listenAddress, listenPort, allowedNetworks, DVMSoftkeys, null, null, txAudioCallback, txAudioSampleRate)
{
this.talkgroups = talkgroups;
this.vChannel = vChannel;
@@ -98,17 +100,29 @@ public override bool ReleaseButton(SoftkeyName name)
Log.Logger.Warning("Talkgroup security is strapped secure, cannot toggle secure mode");
return false;
}
- // If current talkgroup is not set up for encryption, bonk
+ // If current talkgroup is not set up for encryption
if (vChannel.CurrentTalkgroup.KeyId == 0 || vChannel.CurrentTalkgroup.AlgId == P25Defines.P25_ALGO_UNENCRYPT)
{
- Log.Logger.Warning("Talkgroup is not configured for secure operation, cannot toggle secure mode");
- return false;
+ // If we have secure enabled, disable it
+ if (vChannel.Secure)
+ {
+ vChannel.Secure = false;
+ Log.Logger.Information("Disabling secure mode for radio {name:l}, channel not configured for encryption", vChannel.Config.Name);
+ }
+ else
+ {
+ Log.Logger.Warning("Talkgroup is not configured for secure operation, cannot toggle secure mode");
+ return false;
+ }
+ }
+ else
+ {
+ // Toggle secure status
+ vChannel.Secure = !vChannel.Secure;
+ Log.Logger.Information("Toggling secure mode for radio {name:l}: {state:l}", vChannel.Config.Name, (vChannel.Secure ? "ON" : "OFF"));
}
- // Toggle secure status
- vChannel.Secure = !vChannel.Secure;
- Log.Logger.Information("Toggling secure mode for radio {name:l}: {state:l}", vChannel.Config.Name, (vChannel.Secure ? "ON" : "OFF"));
// Return channel setup success/failure
- return vChannel.SetupChannel();
+ return vChannel.SetupChannelCrypto();
// HOME
case SoftkeyName.HOME:
@@ -117,6 +131,10 @@ public override bool ReleaseButton(SoftkeyName name)
// SCAN
case SoftkeyName.SCAN:
return vChannel.ToggleScan();
+
+ // DEL (Nuisance Delete)
+ case SoftkeyName.DEL:
+ return vChannel.NuisanceDelete();
// Handle unhandled buttons
default:
diff --git a/rc2-dvm/VirtualChannel.P25.cs b/rc2-dvm/VirtualChannel.P25.cs
index 6ec7604..675fbe4 100644
--- a/rc2-dvm/VirtualChannel.P25.cs
+++ b/rc2-dvm/VirtualChannel.P25.cs
@@ -454,68 +454,98 @@ private void P25DecodeAudioFrame(byte[] ldu, P25DataReceivedEvent e, P25DUID dui
///
public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
{
- // First, we validate if we should do anything with this data
- // Ignore if we're transmitting
- if (IsTransmitting())
- {
- Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, channel is currently transmitting", Config.Name, e.DstId);
- return;
- }
- // See if we have the TG selected
- if (IsTalkgroupSelected(VocoderMode.P25, e.DstId))
- {
- Log.Logger.Debug("({0:l}) P25 RX {1} {2:l}", Config.Name, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID));
- }
- // See if this TG is configured as an announcement TG
- else if (Config.AnnouncementGroup == e.DstId)
- {
- Log.Logger.Debug("({0:l}) P25 ATG {1} {2:l}", Config.Name, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID));
- }
- // Scan RX handler for talkgroups in the scanlist
- else if (Scanning && HasTgInScanlist(Config.Mode, e.DstId))
- {
- // Ignore if we're within a hang time and a different tg is currently landed
- if (scanHangTimer.Enabled && (scanLandedTg?.DestinationId != e.DstId))
- {
- Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {tgid}, scan hang timer running for another TG ({landedId})", Config.Name, e.DstId, scanLandedTg.DestinationId);
- return;
- }
- // If the talkgroup is in the scanlist, (re)start the scan hang timer and indicate we've landed on a channel
- else
- {
- // Get the talkgroup
- TalkgroupConfigObject? tg = Config.Talkgroups?.FirstOrDefault(t => t.DestinationId == e.DstId);
- // Log Print
- Log.Logger.Debug("({0:l}) P25 SCAN RX {1} ({2:l})", Config.Name, e.DstId, Enum.GetName(e.DUID));
- // Start the scan hang timer and land this channel
- scanHangTimer.Stop();
- scanHangTimer.Start();
- scanLandedTg = tg;
- // Update the channel name
- dvmRadio.Status.ChannelName = tg.Name;
- }
- }
- // Ignore all other conditions
- else
- {
- //Log.Logger.Debug("({0:l) Ignoring data from P25 TGID {1}, not scanning and not configured for this TG", Config.Name, e.DstId);
- return;
- }
-
- // Process the call
+ // Decode basic call info
uint sysId = (uint)((e.Data[11U] << 8) | (e.Data[12U] << 0));
uint netId = FneUtils.Bytes3ToUInt32(e.Data, 16);
byte control = e.Data[14U];
+ // Decode call data into its own array
byte len = e.Data[23];
byte[] data = new byte[len];
for (int i = 24; i < len; i++)
data[i - 24] = e.Data[i];
- // if this is an LDU1 see if this is the first LDU with HDU encryption data
+ // If we're not already ignoring the call, check if we should be
+ if (!ignoreCall)
+ {
+ // Ignore if we're not connected
+ if (!Connected)
+ {
+ Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, channel is not connected", Config.Name, e.DstId);
+ ignoreCall = true;
+ }
+ // Ignore if we're transmitting
+ else if (IsTransmitting())
+ {
+ Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, channel is currently transmitting", Config.Name, e.DstId);
+ ignoreCall = true;
+ }
+ // See if we have the TG selected
+ else if (IsTalkgroupSelected(VocoderMode.P25, e.DstId))
+ {
+ Log.Logger.Debug("({0:l}) P25 RX {1} {2:l}", Config.Name, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID));
+ }
+ // See if this TG is configured as an announcement TG
+ else if (Config.AnnouncementGroup == e.DstId)
+ {
+ Log.Logger.Debug("({0:l}) P25 ATG {1} {2:l}", Config.Name, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID));
+ }
+ // Scan RX handler for talkgroups in the scanlist
+ else if (Scanning && HasTgInScanlist(Config.Mode, e.DstId) && !IsTgNuisanceDeleted(Config.Mode, e.DstId))
+ {
+ // Ignore if we're within a hang time and a different tg is currently landed
+ if (scanHangTimer.Enabled && (scanLandedTg?.DestinationId != e.DstId))
+ {
+ Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {tgid}, scan hang timer running for another TG ({landedId})", Config.Name, e.DstId, scanLandedTg.DestinationId);
+ ignoreCall = true;
+ }
+ // If the talkgroup is in the scanlist, (re)start the scan hang timer and indicate we've landed on a channel
+ else
+ {
+ // Get the talkgroup
+ TalkgroupConfigObject? tg = Config.Talkgroups?.FirstOrDefault(t => t.DestinationId == e.DstId);
+ // Null check
+ if (tg == null)
+ {
+ Log.Logger.Warning("({0:l}) Failed to lookup talkgroup for TGID {tgid}", Config.Name, e.DstId);
+ ignoreCall = true;
+ }
+ else
+ {
+ // Start the scan hang timer and land this channel
+ scanHangTimer.Stop();
+ scanHangTimer.Start();
+ scanLandedTg = tg;
+ // Setup Crypto if needed and ignore the call if it fails
+ if (!SetupChannelCrypto())
+ {
+ Log.Logger.Warning("({0:l}) Failed to setup crypto for scan landed TG {tg:l} ({tgid}), ignoring call", Config.Name, scanLandedTg.Name, scanLandedTg.DestinationId);
+ ignoreCall = true;
+ }
+ else
+ {
+ // Log Print
+ Log.Logger.Debug("({0:l}) P25 SCAN RX {1} ({2:l})", Config.Name, e.DstId, Enum.GetName(e.DUID));
+ // Update the channel name
+ dvmRadio.Status.ChannelName = tg.Name;
+ }
+ }
+ }
+ }
+ // Ignore all other conditions
+ else
+ {
+ Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, not scanning and not configured for this TG", Config.Name, e.DstId);
+ ignoreCall = true;
+ }
+ }
+
+ // if this is an LDU1 and we're not ignoring the call, see if this is the first LDU that contains the MI and other encryption info
if (e.DUID == P25DUID.LDU1 && !ignoreCall)
{
byte frameType = e.Data[180];
+
+ // Get initial MI and enc info from HDU to prevent screech
if (frameType == P25Defines.P25_FT_HDU_VALID)
{
// Get Alg & KID
@@ -523,37 +553,55 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
callKeyId = (ushort)(e.Data[182] << 8 | e.Data[183]);
// Copy MI
Array.Copy(e.Data, 184, callMi, 0, P25Defines.P25_MI_LENGTH);
+
+ Log.Logger.Debug("({0:l}) P25D HDU: Got KID 0x{kid:X4} Algo {algo:l} for TGID {tgid}", Config.Name, callKeyId, Enum.GetName((Algorithm)callAlgoId), e.DstId);
// Only setup crypto for non-clear calls
if (callAlgoId != P25Defines.P25_ALGO_UNENCRYPT)
{
- // Validate key
- if (Config.StrictKeyMapping && callKeyId != CurrentTalkgroup.KeyId)
+ // If we have strict key mapping enabled, the key has to match the talkgroup configuration
+ if (Config.StrictKeyMapping)
{
- Log.Logger.Warning("({0:l}) P25D: Ignoring traffic for non-matching key ID 0x{keyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, callAlgoId);
- ignoreCall = true;
+ // First, check landed scan key
+ if (scanLandedTg != null && callKeyId != scanLandedTg.KeyId)
+ {
+ Log.Logger.Warning("({0:l}) P25D: Ignoring scanning traffic for non-matching key ID 0x{keyID:X4} != 0x{expKeyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, scanLandedTg.KeyId, callAlgoId);
+ ignoreCall = true;
+ }
+ // Next, check selected talgroup key
+ else if (scanLandedTg == null && callKeyId != CurrentTalkgroup.KeyId)
+ {
+ Log.Logger.Warning("({0:l}) P25D: Ignoring traffic for non-matching key ID 0x{keyID:X4} != 0x{expKeyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, CurrentTalkgroup.KeyId, callAlgoId);
+ ignoreCall = true;
+ }
}
+ // Ignore the call if we don't have the key loaded
else if (!loadedKeys.ContainsKey(callKeyId))
{
Log.Logger.Warning("({0:l}) P25D: Ignoring traffic for missing key ID 0x{keyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, callAlgoId);
ignoreCall = true;
}
- else
+ // Assuming all checks passed, set up the crypto engine
+ if (!ignoreCall)
{
// Set Key
crypto.SetKey(callKeyId, callAlgoId, loadedKeys[callKeyId].GetKey());
// Set up crypto engine
crypto.Prepare(callAlgoId, callKeyId, callMi);
// Log
- Log.Logger.Debug("({0:l}) Preparing decryption for Key ID {keyID:X4} ({algo:l})", Config.Name, CurrentTalkgroup.KeyId, Enum.GetName(typeof(Algorithm), CurrentTalkgroup.AlgId));
+ Log.Logger.Debug("({0:l}) Preparing decryption for Key ID {keyID:X4} ({algo:l})", Config.Name, callKeyId, Enum.GetName(typeof(Algorithm), callAlgoId));
}
}
}
}
- // is this a new call stream?
- if (e.StreamId != status[FneSystemBase.P25_FIXED_SLOT].RxStreamId && ((e.DUID != P25DUID.TDU) && (e.DUID != P25DUID.TDULC)))
+ // Check to see if this is a new call stream and initialize the call if so
+ if (!ignoreCall && e.StreamId != status[FneSystemBase.P25_FIXED_SLOT].RxStreamId && ((e.DUID != P25DUID.TDU) && (e.DUID != P25DUID.TDULC)))
{
+ // Debug, dump the LDU
+ Log.Logger.Debug("({0:l}) New call stream, dumping data:", Config.Name);
+ Log.Logger.Debug(FneUtils.HexDump(e.Data));
+
callInProgress = true;
status[FneSystemBase.P25_FIXED_SLOT].RxStart = pktTime;
@@ -563,6 +611,7 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
// Fix incorrect algo/key IDs
if (callAlgoId == 0 && callKeyId == 0)
{
+ Log.Logger.Warning("({0:l}) detected AlgoID 0, resetting to unencrypted!", Config.Name);
callAlgoId = P25Defines.P25_ALGO_UNENCRYPT;
}
@@ -583,78 +632,50 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
// Log
if (callAlgoId != P25Defines.P25_ALGO_UNENCRYPT)
{
- Log.Logger.Information("({0:l}) P25D: Traffic *ENC CALL START* PEER {1} SRC_ID {2} TGID {3} ALGO {4:l} KEY 0x{5:X4} [STREAM ID {6}]", Config.Name, e.PeerId, e.SrcId, e.DstId, Enum.GetName(typeof(Algorithm), callAlgoId), callKeyId, e.StreamId);
+ Log.Logger.Information("({0:l}) P25D: *ENC CALL START* PEER {1} SRC_ID {2} TGID {3} ALGO {4:l} KEY 0x{5:X4} [STREAM ID {6}]", Config.Name, e.PeerId, e.SrcId, e.DstId, Enum.GetName(typeof(Algorithm), callAlgoId), callKeyId, e.StreamId);
}
else
{
- Log.Logger.Information("({0:l}) P25D: Traffic *CALL START * PEER {1} SRC_ID {2} TGID {3} [STREAM ID {4}]", Config.Name, e.PeerId, e.SrcId, e.DstId, e.StreamId);
+ Log.Logger.Information("({0:l}) P25D: *CALL START * PEER {1} SRC_ID {2} TGID {3} [STREAM ID {4}]", Config.Name, e.PeerId, e.SrcId, e.DstId, e.StreamId);
}
// Play a sound if it's an ATG call if so configured
if (e.DstId == Config.AnnouncementGroup && Config.AnnouncementGroupTone)
{
- Log.Logger.Information("({0:l} P25D: ATG CALL START, PLAYING TONE", Config.Name);
+ Log.Logger.Information("({0:l}) P25D: *ATG CALL START* PLAYING TONE", Config.Name);
toneAtgFrameSkip = PlayAtgTone();
}
}
- // Is the call over?
- if (((e.DUID == P25DUID.TDU) || (e.DUID == P25DUID.TDULC)) && (status[FneSystemBase.P25_FIXED_SLOT].RxType != FrameType.TERMINATOR))
+ // Check if the call has ended, and reset our flags if so
+ //if ( ((e.DUID == P25DUID.TDU) || (e.DUID == P25DUID.TDULC)) && (status[FneSystemBase.P25_FIXED_SLOT].RxType != FrameType.TERMINATOR))
+ if (e.DUID == P25DUID.TDU || e.DUID == P25DUID.TDULC)
{
- // Reset flags
- ignoreCall = false;
- callInProgress = false;
- callAlgoId = P25Defines.P25_ALGO_UNENCRYPT;
- TimeSpan callDuration = pktTime - status[FneSystemBase.P25_FIXED_SLOT].RxStart;
- // Update state
- dvmRadio.Status.State = rc2_core.RadioState.Idle;
- // Stop source ID callback
- sourceIdTimer.Stop();
- // Update channel name based on whether it's a landed scan channel or not
- if (scanLandedTg != null)
- dvmRadio.Status.ChannelName = scanLandedTg.Name;
- else
- dvmRadio.Status.ChannelName = CurrentTalkgroup.Name;
- // Stop RX data timeout timer
- rxDataTimer.Stop();
- // Status update
- dvmRadio.StatusCallback();
- // Log
- Log.Logger.Information("({0:l}) P25D: Traffic *CALL END * PEER {1} SRC_ID {2} TGID {3} DUR {4} [STREAM ID {5}]", Config.Name, e.PeerId, e.SrcId, e.DstId, callDuration, e.StreamId);
+ // Log if not ignoring
+ if (!ignoreCall)
+ {
+ TimeSpan callDuration = pktTime - status[FneSystemBase.P25_FIXED_SLOT].RxStart;
+ Log.Logger.Information("({0:l}) P25D: Traffic *CALL END * PEER {1} SRC_ID {2} TGID {3} DUR {4} [STREAM ID {5}]", Config.Name, e.PeerId, e.SrcId, e.DstId, callDuration, e.StreamId);
+ }
+ // Reset call
+ resetCall();
+ // Return
return;
}
- if (ignoreCall && callAlgoId == P25Defines.P25_ALGO_UNENCRYPT)
- ignoreCall = false;
-
- if (e.DUID == P25DUID.LDU2 && !ignoreCall)
- callAlgoId = data[88];
-
+ // At this point, if we're supposed to be ignoring the call, we can return
if (ignoreCall)
return;
- /*if (callAlgoId != P25Defines.P25_ALGO_UNENCRYPT)
- {
- if (status[FneSystemBase.P25_FIXED_SLOT].RxType != FrameType.TERMINATOR)
- {
- callInProgress = false;
- TimeSpan callDuration = pktTime - status[FneSystemBase.P25_FIXED_SLOT].RxStart;
- Log.Logger.Information($"({Config.Name}) P25D: Traffic *CALL END (T) * PEER {e.PeerId} SRC_ID {e.SrcId} TGID {e.DstId} DUR {callDuration} [STREAM ID {e.StreamId}]");
- }
-
- // Send an extra block of silent PCM samples to prevent the weird artifacting at the end of calls
- dvmRadio.RxSendPCM16Samples(silence, FneSystemBase.SAMPLE_RATE);
-
- ignoreCall = true;
- return;
- }*/
+ // Grab the algo ID from LDU2
+ if (e.DUID == P25DUID.LDU2)
+ callAlgoId = data[88];
- // At this point we chan check for late entry
- if (!ignoreCall && !callInProgress)
+ // At this point, if we're not ignoring the call, not a new call stream, and don't have a call in progress, it's probably late entry
+ if (!callInProgress)
{
callInProgress = true;
- //callAlgoId = P25Defines.P25_ALGO_UNENCRYPT;
status[FneSystemBase.P25_FIXED_SLOT].RxStart = pktTime;
// Update status
dvmRadio.Status.State = rc2_core.RadioState.Receiving;
@@ -796,6 +817,7 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
if (callMi != null)
crypto.Prepare(callAlgoId, callKeyId, callMi);
+ // Store current variables
status[FneSystemBase.P25_FIXED_SLOT].RxRFS = e.SrcId;
status[FneSystemBase.P25_FIXED_SLOT].RxType = e.FrameType;
status[FneSystemBase.P25_FIXED_SLOT].RxTGId = e.DstId;
diff --git a/rc2-dvm/VirtualChannel.cs b/rc2-dvm/VirtualChannel.cs
index c6067b9..3fd5e26 100644
--- a/rc2-dvm/VirtualChannel.cs
+++ b/rc2-dvm/VirtualChannel.cs
@@ -26,6 +26,7 @@
using NWaves.Operations;
using Org.BouncyCastle.Asn1;
using NAudio.Midi;
+using System.Linq.Expressions;
namespace rc2_dvm
{
@@ -94,6 +95,11 @@ public partial class VirtualChannel
///
public bool Secure = false;
+ ///
+ /// State variable to track if SetupCryptoConfig() has been called for the current call
+ ///
+ private bool cryptoConfigured = false;
+
///
/// Index of the currently selected talkgroup for this channel
///
@@ -125,6 +131,22 @@ public TalkgroupConfigObject CurrentTalkgroup
}
}
+ ///
+ /// Whether the virtual channel is currently connected to the console client
+ ///
+ public bool Connected
+ {
+ get
+ {
+ return (
+ dvmRadio.Status.State == RadioState.Idle ||
+ dvmRadio.Status.State == RadioState.Transmitting ||
+ dvmRadio.Status.State == RadioState.Receiving ||
+ dvmRadio.Status.State == RadioState.Encrypted
+ );
+ }
+ }
+
///
/// The talkgroup currently being transmitted on
///
@@ -349,6 +371,7 @@ public VirtualChannel(VirtualChannelConfigObject config, KeyContainer keyContain
dvmRadio = new DVMRadio(
Config.Name, Config.RxOnly,
Config.ListenAddress, Config.ListenPort,
+ RC2DVM.Configuration.Network.AllowedNetworks,
Config.Talkgroups, this,
HandleTxAudio,
waveFormat.SampleRate
@@ -454,7 +477,7 @@ public bool ChannelUp()
// Log
Log.Logger.Debug("({0:l}) Selected TG {1:l} ({2})", Config.Name, CurrentTalkgroup.Name, CurrentTalkgroup.DestinationId);
// Return channel setup success
- return SetupChannel();
+ return SetupChannelCrypto();
}
}
@@ -483,7 +506,7 @@ public bool ChannelDown()
// Log
Log.Logger.Debug("({0:l}) Selected TG {1:l} ({2})", Config.Name, CurrentTalkgroup.Name, CurrentTalkgroup.DestinationId);
// Return channel setup success
- return SetupChannel();
+ return SetupChannelCrypto();
}
else { return false; }
}
@@ -517,44 +540,64 @@ public bool ChannelIndex(int index)
// Log
Log.Logger.Debug("({0:l}) Selected TG {1:l} ({2})", Config.Name, CurrentTalkgroup.Name, CurrentTalkgroup.DestinationId);
// Return setup success
- return SetupChannel();
+ return SetupChannelCrypto();
}
///
/// Callback when a new channel is selected (handles configuration of encryption, etc)
///
- public bool SetupChannel()
+ public bool SetupChannelCrypto()
{
- // Setup encryption if configured
- if (CurrentTalkgroup.AlgId != P25Defines.P25_ALGO_UNENCRYPT)
+ // Return true if we already did it
+ if (cryptoConfigured == true)
+ {
+ return true;
+ }
+
+ // Determine which TG we should be configuring for (scan TG or selected TG)
+ TalkgroupConfigObject tg = CurrentTalkgroup;
+ if (scanLandedTg != null)
{
- // Ensure key ID is set
- if (CurrentTalkgroup.KeyId == 0)
+ tg = scanLandedTg;
+ Log.Logger.Debug("({0:l}) configuring crypto for scan landed TG {TG:l} ({tgid})", Config.Name, tg.Name, tg.DestinationId);
+ }
+ else
+ {
+ Log.Logger.Debug("({0:l}) configuring crypto for selected TG {TG:l} ({tgid})", Config.Name, tg.Name, tg.DestinationId);
+ }
+
+ // Setup crypto if required
+ if (tg.AlgId != P25Defines.P25_ALGO_UNENCRYPT)
+ {
+ // Check for proper config
+ if (tg.KeyId == 0)
{
- Log.Logger.Error("({0:l}) KEYFAIL: {TG} ({TGID}) is configured for encryption but has Key ID 0", Config.Name, CurrentTalkgroup.Name, CurrentTalkgroup.DestinationId);
+ Log.Logger.Error("({0:l}) KEYFAIL: TG {TG:l} ({TGID}) is configured for encryption but has Key ID 0", Config.Name, tg.Name, tg.DestinationId);
return false;
}
- // Load the key if it's not loaded already
- if (!loadedKeys.ContainsKey(CurrentTalkgroup.KeyId))
+ // Log Print
+ Log.Logger.Debug("({0:l}) Loading key for TG {tg:l} ({tgid}): {alg:l} KID 0x{kid:X4}", Config.Name, tg.Name, tg.DestinationId, Enum.GetName((Algorithm)tg.AlgId), tg.KeyId);
+ // Load key if not already loaded
+ if (!loadedKeys.ContainsKey(tg.KeyId))
{
- // Try to get the key from the local file
- KeyItem key = keyContainer.GetKeyById(CurrentTalkgroup.KeyId);
+ KeyItem key = keyContainer.GetKeyById(tg.KeyId);
if (key != null)
{
loadedKeys[key.KeyId] = key;
- Log.Logger.Information("({0:l}) Loaded Key ID 0x{KeyId:X4} ({Algo:l}) from keyfile into local keystore", Config.Name, key.KeyId, Enum.GetName(typeof(Algorithm), key.KeyFormat));
+ Log.Logger.Information("({0:l}) Loaded KID 0x{KeyId:X4} ({Algo:l}) from keyfile into local keystore", Config.Name, key.KeyId, Enum.GetName(typeof(Algorithm), key.KeyFormat));
}
- // Request from FNE as a fallback
+ // If we couldn't find it locally, request it from the master and return false (will ignore this call)
else
{
- Log.Logger.Information("({0:l}) Key ID 0x{keyId:X4} not found in local keyfile, requesting from FNE", Config.Name, CurrentTalkgroup.KeyId);
- RC2DVM.fneSystem.peer.SendMasterKeyRequest(CurrentTalkgroup.AlgId, CurrentTalkgroup.KeyId);
+ Log.Logger.Warning("({0:l}) KID 0x{keyId:X4} not found in local keyfile, requesting from FNE", Config.Name, tg.KeyId);
+ RC2DVM.fneSystem.peer.SendMasterKeyRequest(tg.AlgId, tg.KeyId);
+ return false;
}
}
- }
+ }
// Update secure softkey & radio state
- if (CurrentTalkgroup.Strapped || Secure)
+ if (tg.Strapped || Secure)
{
// Update status
dvmRadio.Status.Secure = true;
@@ -575,6 +618,7 @@ public bool SetupChannel()
dvmRadio.StatusCallback();
// Return true if nothing failed
+ cryptoConfigured = true;
return true;
}
@@ -626,18 +670,30 @@ private void resetCall()
{
// Stop source ID callback
sourceIdTimer.Stop();
+ // Update channel name based on whether it's a landed scan channel or not
+ if (scanLandedTg != null)
+ dvmRadio.Status.ChannelName = scanLandedTg.Name;
+ else
+ dvmRadio.Status.ChannelName = CurrentTalkgroup.Name;
// Stop rx data timeout timer
rxDataTimer.Stop();
// Reset P25 counter
p25N = 0;
- // Update status
- dvmRadio.Status.State = RadioState.Idle;
+ // Reset crypto configuration flag
+ cryptoConfigured = false;
+ // Update status to idle if connected
+ if (Connected)
+ dvmRadio.Status.State = RadioState.Idle;
+ // Reset flags
ignoreCall = false;
+ callInProgress = false;
+ // Reset encryption
callAlgoId = P25Defines.P25_ALGO_UNENCRYPT;
FneUtils.Memset(callMi, 0x00, P25Defines.P25_MI_LENGTH);
- callInProgress = false;
// Send status
dvmRadio.StatusCallback();
+ // Log
+ Log.Logger.Debug("({0:l}) reset call states", Config.Name);
}
///
@@ -719,6 +775,28 @@ public bool HasTgInScanlist(VocoderMode mode, uint tgid, uint slot = 1)
}
}
+ ///
+ /// Returns whether the given talkgroup is currently nuisance deleted
+ ///
+ ///
+ ///
+ ///
+ ///
+
+ public bool IsTgNuisanceDeleted(VocoderMode mode, uint tgid, uint slot = 1)
+ {
+ if (mode != Config.Mode) { return false; }
+
+ if (Config.Mode == VocoderMode.DMR)
+ {
+ return Config.Talkgroups?.Any(tg => tg.DestinationId == tgid && tg.Timeslot == slot && tg.NuisanceDeleted == true) ?? false;
+ }
+ else
+ {
+ return Config.Talkgroups?.Any(tg => tg.DestinationId == tgid && tg.NuisanceDeleted == true) ?? false;
+ }
+ }
+
///
/// Initiate transmit to the system
///
@@ -886,7 +964,7 @@ public void HandleTxAudio(short[] pcm16Samples)
private void LoadSounds()
{
- Log.Logger.Debug("Loading sounds for virtual channel");
+ Log.Logger.Debug("({0:l}) Loading sounds for virtual channel", Config.Name);
// Load ATG tone
WaveFile atgTone = new WaveFile(rc2_dvm.Properties.Resources.sndAtgTone);
@@ -925,7 +1003,7 @@ public int PlayAtgTone()
// Calculate the number of MBE frames to skip
int skipFrames = toneAtg.Length / LDU_SAMPLES_LENGTH;
// Debug print
- Log.Logger.Debug("Sending ATG tone to Radio ({0} samples / {1} LDU frames)", toneAtg.Length, skipFrames);
+ Log.Logger.Debug("({0:l}) Sending ATG tone to Radio ({0} samples / {1} LDU frames)", Config.Name, toneAtg.Length, skipFrames);
// Send audio
dvmRadio.RxSendPCM16Samples(toneAtg, (uint)waveFormat.SampleRate);
// Return
@@ -944,6 +1022,18 @@ public bool GoHome()
return ChannelIndex(homeTalkgroupIndex);
}
+ ///
+ /// Reset all nuisance-deleted talkgroups in the channel
+ ///
+ private void resetNuisanceDeletions()
+ {
+ Log.Logger.Information("({0:l}) resetting nuisance deletions", Config.Name);
+ Config.Talkgroups.ForEach(tg =>
+ {
+ tg.NuisanceDeleted = false;
+ });
+ }
+
///
/// Toggle the scan state of the virtual channel
///
@@ -955,8 +1045,14 @@ public bool ToggleScan()
Log.Logger.Debug("({0:l}) Scan disabled, stopping hang timer", Config.Name);
scanHangTimer.Stop();
scanLandedTg = null;
+ // Reset nuisance
+ resetNuisanceDeletions();
// Update radio state
dvmRadio.Status.ScanState = ScanState.NotScanning;
+ // Stop the ID callback
+ sourceIdTimer.Stop();
+ // Revert to the currently selected TG name
+ dvmRadio.Status.ChannelName = CurrentTalkgroup.Name;
// Update softkey
int keyIdx = dvmRadio.Status.Softkeys.FindIndex(key => key.Name == SoftkeyName.SCAN);
dvmRadio.Status.Softkeys[keyIdx].State = SoftkeyState.Off;
@@ -976,5 +1072,42 @@ public bool ToggleScan()
// Always return true for now (TODO: Return false for invalid scan configurations)
return true;
}
+
+ ///
+ /// Nuisance delete button handler
+ ///
+ ///
+ public bool NuisanceDelete()
+ {
+ // Do nothing if we're not scanning
+ if (!Scanning)
+ {
+ Log.Logger.Warning("({0:l}) Channel not scanning, cannot nuisance delete", Config.Name);
+ return false;
+ }
+ // Do nothing if we're not landed on a scanned channel
+ if (scanLandedTg == null)
+ {
+ Log.Logger.Warning("({0:l}) Scan not landed, cannot nuisance delete", Config.Name);
+ return false;
+ }
+ // Find the scanned talkgroup in our config list
+ int tgIdx = Config.Talkgroups.FindIndex(tg => tg.DestinationId == scanLandedTg.DestinationId && tg.Timeslot == scanLandedTg.Timeslot);
+ // Sanity check
+ if (tgIdx == -1)
+ {
+ Log.Logger.Error("({0:l}) Failed to lookup TGID {tgid} TS {ts} in talkgroup list!", Config.Name, scanLandedTg.DestinationId, scanLandedTg.Timeslot);
+ return false;
+ }
+ // Nuisance delete
+ Config.Talkgroups[tgIdx].NuisanceDeleted = true;
+ // Revert scan status vars
+ scanHangTimer.Stop();
+ scanLandedTg = null;
+ // Reset all call parameters
+ resetCall();
+ // Return true if everything was okay
+ return true;
+ }
}
}
diff --git a/rc2-dvm/config.example.yml b/rc2-dvm/config.example.yml
index ee1cc47..f41e233 100644
--- a/rc2-dvm/config.example.yml
+++ b/rc2-dvm/config.example.yml
@@ -55,6 +55,11 @@ network:
# Whether additional debug is printed to the log
debug: false
+ # Optional allowed networks, helps with filtering from consoles running on PCs with lots of interfaces
+ allowedNetworks:
+ - 192.168.0.0/24
+ - 10.10.0.0/16
+
#
# Global Audio Config
#
diff --git a/rc2-dvm/rc2-dvm.csproj b/rc2-dvm/rc2-dvm.csproj
index ec165db..804c85f 100644
--- a/rc2-dvm/rc2-dvm.csproj
+++ b/rc2-dvm/rc2-dvm.csproj
@@ -43,7 +43,7 @@
-
+
From fb320d2f95e25c66e383b25972df623c161abc3c Mon Sep 17 00:00:00 2001
From: W3AXL <29879554+W3AXL@users.noreply.github.com>
Date: Sat, 23 May 2026 21:01:50 -0500
Subject: [PATCH 2/4] finally got call handling into a mostly-working state
---
rc2-dvm/VirtualChannel.P25.cs | 152 ++++++++++++++++++----------------
rc2-dvm/VirtualChannel.cs | 50 ++++++++++-
2 files changed, 131 insertions(+), 71 deletions(-)
diff --git a/rc2-dvm/VirtualChannel.P25.cs b/rc2-dvm/VirtualChannel.P25.cs
index 675fbe4..7f4c7a4 100644
--- a/rc2-dvm/VirtualChannel.P25.cs
+++ b/rc2-dvm/VirtualChannel.P25.cs
@@ -26,8 +26,6 @@ public partial class VirtualChannel
private byte[] netLDU2 = new byte[9 * 25];
private uint p25SeqNo = 0;
private byte p25N = 0;
-
- private bool ignoreCall = false;
// Encryption params
private byte callAlgoId = P25Defines.P25_ALGO_UNENCRYPT;
@@ -465,83 +463,92 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
for (int i = 24; i < len; i++)
data[i - 24] = e.Data[i];
- // If we're not already ignoring the call, check if we should be
- if (!ignoreCall)
+ // Check if we're already ignoring this call and return
+ if (ignoringCall(e))
{
- // Ignore if we're not connected
- if (!Connected)
- {
- Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, channel is not connected", Config.Name, e.DstId);
- ignoreCall = true;
- }
- // Ignore if we're transmitting
- else if (IsTransmitting())
- {
- Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, channel is currently transmitting", Config.Name, e.DstId);
- ignoreCall = true;
- }
- // See if we have the TG selected
- else if (IsTalkgroupSelected(VocoderMode.P25, e.DstId))
- {
- Log.Logger.Debug("({0:l}) P25 RX {1} {2:l}", Config.Name, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID));
- }
- // See if this TG is configured as an announcement TG
- else if (Config.AnnouncementGroup == e.DstId)
+ return;
+ }
+
+ // Check for any of the reasons we should be ignoring call data
+ // Ignore if we're not configure for this TG
+ if (!HasTalkgroupConfigured(VocoderMode.P25, e.DstId))
+ {
+ Log.Logger.Debug("({0:l} Ignoring data from P25 TGID {1}, channel does not have TGID configured", Config.Name, e.DstId);
+ ignoreCall(e);
+ }
+ // Ignore if we're not connected
+ else if (!Connected)
+ {
+ Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, channel is not connected", Config.Name, e.DstId);
+ ignoreCall(e);
+ }
+ // Ignore if we're transmitting
+ else if (IsTransmitting())
+ {
+ Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, channel is currently transmitting", Config.Name, e.DstId);
+ ignoreCall(e);
+ }
+ // See if we have the TG selected
+ else if (IsTalkgroupSelected(VocoderMode.P25, e.DstId))
+ {
+ Log.Logger.Debug("({0:l}) P25 RX {1} {2:l}", Config.Name, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID));
+ }
+ // See if this TG is configured as an announcement TG
+ else if (Config.AnnouncementGroup == e.DstId)
+ {
+ Log.Logger.Debug("({0:l}) P25 ATG {1} {2:l}", Config.Name, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID));
+ }
+ // Scan RX handler for talkgroups in the scanlist
+ else if (Scanning && HasTgInScanlist(Config.Mode, e.DstId) && !IsTgNuisanceDeleted(Config.Mode, e.DstId))
+ {
+ // Ignore if we're within a hang time and a different tg is currently landed
+ if (scanHangTimer.Enabled && (scanLandedTg?.DestinationId != e.DstId))
{
- Log.Logger.Debug("({0:l}) P25 ATG {1} {2:l}", Config.Name, e.DstId, Enum.GetName(typeof(P25DUID), e.DUID));
+ Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {tgid}, scan hang timer running for another TG ({landedId})", Config.Name, e.DstId, scanLandedTg.DestinationId);
+ ignoreCall(e);
}
- // Scan RX handler for talkgroups in the scanlist
- else if (Scanning && HasTgInScanlist(Config.Mode, e.DstId) && !IsTgNuisanceDeleted(Config.Mode, e.DstId))
+ // If the talkgroup is in the scanlist, (re)start the scan hang timer and indicate we've landed on a channel
+ else
{
- // Ignore if we're within a hang time and a different tg is currently landed
- if (scanHangTimer.Enabled && (scanLandedTg?.DestinationId != e.DstId))
+ // Get the talkgroup
+ TalkgroupConfigObject? tg = Config.Talkgroups?.FirstOrDefault(t => t.DestinationId == e.DstId);
+ // Null check
+ if (tg == null)
{
- Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {tgid}, scan hang timer running for another TG ({landedId})", Config.Name, e.DstId, scanLandedTg.DestinationId);
- ignoreCall = true;
+ Log.Logger.Warning("({0:l}) Failed to lookup talkgroup for TGID {tgid}", Config.Name, e.DstId);
+ ignoreCall(e);
}
- // If the talkgroup is in the scanlist, (re)start the scan hang timer and indicate we've landed on a channel
else
{
- // Get the talkgroup
- TalkgroupConfigObject? tg = Config.Talkgroups?.FirstOrDefault(t => t.DestinationId == e.DstId);
- // Null check
- if (tg == null)
+ // Start the scan hang timer and land this channel
+ scanHangTimer.Stop();
+ scanHangTimer.Start();
+ scanLandedTg = tg;
+ // Setup Crypto if needed and ignore the call if it fails
+ if (!SetupChannelCrypto())
{
- Log.Logger.Warning("({0:l}) Failed to lookup talkgroup for TGID {tgid}", Config.Name, e.DstId);
- ignoreCall = true;
+ Log.Logger.Warning("({0:l}) Failed to setup crypto for scan landed TG {tg:l} ({tgid}), ignoring call", Config.Name, scanLandedTg.Name, scanLandedTg.DestinationId);
+ ignoreCall(e);
}
else
{
- // Start the scan hang timer and land this channel
- scanHangTimer.Stop();
- scanHangTimer.Start();
- scanLandedTg = tg;
- // Setup Crypto if needed and ignore the call if it fails
- if (!SetupChannelCrypto())
- {
- Log.Logger.Warning("({0:l}) Failed to setup crypto for scan landed TG {tg:l} ({tgid}), ignoring call", Config.Name, scanLandedTg.Name, scanLandedTg.DestinationId);
- ignoreCall = true;
- }
- else
- {
- // Log Print
- Log.Logger.Debug("({0:l}) P25 SCAN RX {1} ({2:l})", Config.Name, e.DstId, Enum.GetName(e.DUID));
- // Update the channel name
- dvmRadio.Status.ChannelName = tg.Name;
- }
+ // Log Print
+ Log.Logger.Debug("({0:l}) P25 SCAN RX {1} ({2:l})", Config.Name, e.DstId, Enum.GetName(e.DUID));
+ // Update the channel name
+ dvmRadio.Status.ChannelName = tg.Name;
}
}
}
- // Ignore all other conditions
- else
- {
- Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, not scanning and not configured for this TG", Config.Name, e.DstId);
- ignoreCall = true;
- }
+ }
+ // Ignore all other conditions
+ else
+ {
+ Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, not scanning and not configured for this TG", Config.Name, e.DstId);
+ ignoreCall(e);
}
// if this is an LDU1 and we're not ignoring the call, see if this is the first LDU that contains the MI and other encryption info
- if (e.DUID == P25DUID.LDU1 && !ignoreCall)
+ if (e.DUID == P25DUID.LDU1 && !ignoringCall(e))
{
byte frameType = e.Data[180];
@@ -566,23 +573,23 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
if (scanLandedTg != null && callKeyId != scanLandedTg.KeyId)
{
Log.Logger.Warning("({0:l}) P25D: Ignoring scanning traffic for non-matching key ID 0x{keyID:X4} != 0x{expKeyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, scanLandedTg.KeyId, callAlgoId);
- ignoreCall = true;
+ ignoreCall(e);
}
// Next, check selected talgroup key
else if (scanLandedTg == null && callKeyId != CurrentTalkgroup.KeyId)
{
Log.Logger.Warning("({0:l}) P25D: Ignoring traffic for non-matching key ID 0x{keyID:X4} != 0x{expKeyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, CurrentTalkgroup.KeyId, callAlgoId);
- ignoreCall = true;
+ ignoreCall(e);
}
}
// Ignore the call if we don't have the key loaded
else if (!loadedKeys.ContainsKey(callKeyId))
{
Log.Logger.Warning("({0:l}) P25D: Ignoring traffic for missing key ID 0x{keyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, callAlgoId);
- ignoreCall = true;
+ ignoreCall(e);
}
// Assuming all checks passed, set up the crypto engine
- if (!ignoreCall)
+ if (!ignoringCall(e))
{
// Set Key
crypto.SetKey(callKeyId, callAlgoId, loadedKeys[callKeyId].GetKey());
@@ -596,7 +603,7 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
}
// Check to see if this is a new call stream and initialize the call if so
- if (!ignoreCall && e.StreamId != status[FneSystemBase.P25_FIXED_SLOT].RxStreamId && ((e.DUID != P25DUID.TDU) && (e.DUID != P25DUID.TDULC)))
+ if (!ignoringCall(e) && e.StreamId != status[FneSystemBase.P25_FIXED_SLOT].RxStreamId && ((e.DUID != P25DUID.TDU) && (e.DUID != P25DUID.TDULC)))
{
// Debug, dump the LDU
Log.Logger.Debug("({0:l}) New call stream, dumping data:", Config.Name);
@@ -653,19 +660,24 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
if (e.DUID == P25DUID.TDU || e.DUID == P25DUID.TDULC)
{
// Log if not ignoring
- if (!ignoreCall)
+ if (!ignoringCall(e))
{
TimeSpan callDuration = pktTime - status[FneSystemBase.P25_FIXED_SLOT].RxStart;
Log.Logger.Information("({0:l}) P25D: Traffic *CALL END * PEER {1} SRC_ID {2} TGID {3} DUR {4} [STREAM ID {5}]", Config.Name, e.PeerId, e.SrcId, e.DstId, callDuration, e.StreamId);
+ // Reset call
+ resetCall();
+ }
+ // If we are ignoring the call, and it's over, we can remove it from our ignored calls list
+ else
+ {
+ clearIgnored(e);
}
- // Reset call
- resetCall();
// Return
return;
}
// At this point, if we're supposed to be ignoring the call, we can return
- if (ignoreCall)
+ if (ignoringCall(e))
return;
// Grab the algo ID from LDU2
diff --git a/rc2-dvm/VirtualChannel.cs b/rc2-dvm/VirtualChannel.cs
index 3fd5e26..b9c38b5 100644
--- a/rc2-dvm/VirtualChannel.cs
+++ b/rc2-dvm/VirtualChannel.cs
@@ -157,6 +157,21 @@ public bool Connected
///
private int homeTalkgroupIndex = -1;
+ ///
+ /// A struct representing a unique call we can check against for processing
+ ///
+ private struct uniqueCall
+ {
+ public uint PeerID;
+ public uint SrcID;
+ public uint DstID;
+ }
+
+ ///
+ /// A list of calls we're currently ignoring
+ ///
+ private List ignoredCalls = new List();
+
///
/// Creates a new instance of a virtual channel
///
@@ -685,7 +700,6 @@ private void resetCall()
if (Connected)
dvmRadio.Status.State = RadioState.Idle;
// Reset flags
- ignoreCall = false;
callInProgress = false;
// Reset encryption
callAlgoId = P25Defines.P25_ALGO_UNENCRYPT;
@@ -1109,5 +1123,39 @@ public bool NuisanceDelete()
// Return true if everything was okay
return true;
}
+
+ ///
+ /// Returns true if a call with the given peer/src/dst combo is present in our ignored calls list
+ ///
+ /// P25DataReceviedEvent for the given call
+ ///
+ private bool ignoringCall(P25DataReceivedEvent e)
+ {
+ return ignoredCalls.Any(call => call.PeerID == e.PeerId && call.SrcID == e.SrcId && call.DstID == e.DstId);
+ }
+
+ ///
+ /// Add a call with the given peer/src/dst to our ignored calls list
+ ///
+ /// P25DataReceviedEvent for the given call
+ private void ignoreCall(P25DataReceivedEvent e)
+ {
+ if (!ignoringCall(e))
+ {
+ ignoredCalls.Add(new uniqueCall { PeerID = e.PeerId, SrcID = e.SrcId, DstID = e.DstId});
+ }
+ }
+
+ ///
+ /// Remove a call with the given peer/src/dst from our ignored calls list
+ ///
+ ///
+ private void clearIgnored(P25DataReceivedEvent e)
+ {
+ if (ignoringCall(e))
+ {
+ ignoredCalls.Remove(new uniqueCall { PeerID = e.PeerId, SrcID = e.SrcId, DstID = e.DstId });
+ }
+ }
}
}
From 17820ddb34ca1abdf6a0575c3ea51b4b1eab82be Mon Sep 17 00:00:00 2001
From: W3AXL <29879554+W3AXL@users.noreply.github.com>
Date: Sat, 23 May 2026 21:34:33 -0500
Subject: [PATCH 3/4] fixed logic for crypto configuration during scan
---
rc2-dvm/VirtualChannel.P25.cs | 47 +++++++++++++-----------
rc2-dvm/VirtualChannel.cs | 68 +++++++++++++++--------------------
2 files changed, 54 insertions(+), 61 deletions(-)
diff --git a/rc2-dvm/VirtualChannel.P25.cs b/rc2-dvm/VirtualChannel.P25.cs
index 7f4c7a4..0784d64 100644
--- a/rc2-dvm/VirtualChannel.P25.cs
+++ b/rc2-dvm/VirtualChannel.P25.cs
@@ -464,7 +464,7 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
data[i - 24] = e.Data[i];
// Check if we're already ignoring this call and return
- if (ignoringCall(e))
+ if (ignoringStream(e.StreamId))
{
return;
}
@@ -474,19 +474,19 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
if (!HasTalkgroupConfigured(VocoderMode.P25, e.DstId))
{
Log.Logger.Debug("({0:l} Ignoring data from P25 TGID {1}, channel does not have TGID configured", Config.Name, e.DstId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
// Ignore if we're not connected
else if (!Connected)
{
Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, channel is not connected", Config.Name, e.DstId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
// Ignore if we're transmitting
else if (IsTransmitting())
{
Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, channel is currently transmitting", Config.Name, e.DstId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
// See if we have the TG selected
else if (IsTalkgroupSelected(VocoderMode.P25, e.DstId))
@@ -505,7 +505,7 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
if (scanHangTimer.Enabled && (scanLandedTg?.DestinationId != e.DstId))
{
Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {tgid}, scan hang timer running for another TG ({landedId})", Config.Name, e.DstId, scanLandedTg.DestinationId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
// If the talkgroup is in the scanlist, (re)start the scan hang timer and indicate we've landed on a channel
else
@@ -516,19 +516,24 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
if (tg == null)
{
Log.Logger.Warning("({0:l}) Failed to lookup talkgroup for TGID {tgid}", Config.Name, e.DstId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
else
{
- // Start the scan hang timer and land this channel
+ // (re)start the scan hang timer
scanHangTimer.Stop();
scanHangTimer.Start();
- scanLandedTg = tg;
+ // Update landed TGID and reset crypto if it's changed
+ if (scanLandedTg != tg)
+ {
+ scanLandedTg = tg;
+ cryptoConfigured = false;
+ }
// Setup Crypto if needed and ignore the call if it fails
if (!SetupChannelCrypto())
{
Log.Logger.Warning("({0:l}) Failed to setup crypto for scan landed TG {tg:l} ({tgid}), ignoring call", Config.Name, scanLandedTg.Name, scanLandedTg.DestinationId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
else
{
@@ -544,11 +549,11 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
else
{
Log.Logger.Debug("({0:l}) Ignoring data from P25 TGID {1}, not scanning and not configured for this TG", Config.Name, e.DstId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
// if this is an LDU1 and we're not ignoring the call, see if this is the first LDU that contains the MI and other encryption info
- if (e.DUID == P25DUID.LDU1 && !ignoringCall(e))
+ if (e.DUID == P25DUID.LDU1 && !ignoringStream(e.StreamId))
{
byte frameType = e.Data[180];
@@ -573,23 +578,23 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
if (scanLandedTg != null && callKeyId != scanLandedTg.KeyId)
{
Log.Logger.Warning("({0:l}) P25D: Ignoring scanning traffic for non-matching key ID 0x{keyID:X4} != 0x{expKeyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, scanLandedTg.KeyId, callAlgoId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
// Next, check selected talgroup key
else if (scanLandedTg == null && callKeyId != CurrentTalkgroup.KeyId)
{
Log.Logger.Warning("({0:l}) P25D: Ignoring traffic for non-matching key ID 0x{keyID:X4} != 0x{expKeyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, CurrentTalkgroup.KeyId, callAlgoId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
}
// Ignore the call if we don't have the key loaded
else if (!loadedKeys.ContainsKey(callKeyId))
{
Log.Logger.Warning("({0:l}) P25D: Ignoring traffic for missing key ID 0x{keyID:X4} (AlgId 0x{algid:X2})", Config.Name, callKeyId, callAlgoId);
- ignoreCall(e);
+ ignoreStream(e.StreamId);
}
// Assuming all checks passed, set up the crypto engine
- if (!ignoringCall(e))
+ if (!ignoringStream(e.StreamId))
{
// Set Key
crypto.SetKey(callKeyId, callAlgoId, loadedKeys[callKeyId].GetKey());
@@ -603,11 +608,11 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
}
// Check to see if this is a new call stream and initialize the call if so
- if (!ignoringCall(e) && e.StreamId != status[FneSystemBase.P25_FIXED_SLOT].RxStreamId && ((e.DUID != P25DUID.TDU) && (e.DUID != P25DUID.TDULC)))
+ if (!ignoringStream(e.StreamId) && e.StreamId != status[FneSystemBase.P25_FIXED_SLOT].RxStreamId && ((e.DUID != P25DUID.TDU) && (e.DUID != P25DUID.TDULC)))
{
// Debug, dump the LDU
- Log.Logger.Debug("({0:l}) New call stream, dumping data:", Config.Name);
- Log.Logger.Debug(FneUtils.HexDump(e.Data));
+ //Log.Logger.Debug("({0:l}) New call stream, dumping data:", Config.Name);
+ //Log.Logger.Debug(FneUtils.HexDump(e.Data));
callInProgress = true;
status[FneSystemBase.P25_FIXED_SLOT].RxStart = pktTime;
@@ -660,7 +665,7 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
if (e.DUID == P25DUID.TDU || e.DUID == P25DUID.TDULC)
{
// Log if not ignoring
- if (!ignoringCall(e))
+ if (!ignoringStream(e.StreamId))
{
TimeSpan callDuration = pktTime - status[FneSystemBase.P25_FIXED_SLOT].RxStart;
Log.Logger.Information("({0:l}) P25D: Traffic *CALL END * PEER {1} SRC_ID {2} TGID {3} DUR {4} [STREAM ID {5}]", Config.Name, e.PeerId, e.SrcId, e.DstId, callDuration, e.StreamId);
@@ -670,14 +675,14 @@ public void P25DataReceived(P25DataReceivedEvent e, DateTime pktTime)
// If we are ignoring the call, and it's over, we can remove it from our ignored calls list
else
{
- clearIgnored(e);
+ clearIgnored(e.StreamId);
}
// Return
return;
}
// At this point, if we're supposed to be ignoring the call, we can return
- if (ignoringCall(e))
+ if (ignoringStream(e.StreamId))
return;
// Grab the algo ID from LDU2
diff --git a/rc2-dvm/VirtualChannel.cs b/rc2-dvm/VirtualChannel.cs
index b9c38b5..702039d 100644
--- a/rc2-dvm/VirtualChannel.cs
+++ b/rc2-dvm/VirtualChannel.cs
@@ -157,20 +157,10 @@ public bool Connected
///
private int homeTalkgroupIndex = -1;
- ///
- /// A struct representing a unique call we can check against for processing
- ///
- private struct uniqueCall
- {
- public uint PeerID;
- public uint SrcID;
- public uint DstID;
- }
-
///
/// A list of calls we're currently ignoring
///
- private List ignoredCalls = new List();
+ private List ignoredStreams = new List();
///
/// Creates a new instance of a virtual channel
@@ -492,6 +482,7 @@ public bool ChannelUp()
// Log
Log.Logger.Debug("({0:l}) Selected TG {1:l} ({2})", Config.Name, CurrentTalkgroup.Name, CurrentTalkgroup.DestinationId);
// Return channel setup success
+ cryptoConfigured = false;
return SetupChannelCrypto();
}
}
@@ -521,6 +512,7 @@ public bool ChannelDown()
// Log
Log.Logger.Debug("({0:l}) Selected TG {1:l} ({2})", Config.Name, CurrentTalkgroup.Name, CurrentTalkgroup.DestinationId);
// Return channel setup success
+ cryptoConfigured = false;
return SetupChannelCrypto();
}
else { return false; }
@@ -555,6 +547,7 @@ public bool ChannelIndex(int index)
// Log
Log.Logger.Debug("({0:l}) Selected TG {1:l} ({2})", Config.Name, CurrentTalkgroup.Name, CurrentTalkgroup.DestinationId);
// Return setup success
+ cryptoConfigured = false;
return SetupChannelCrypto();
}
@@ -569,6 +562,12 @@ public bool SetupChannelCrypto()
return true;
}
+ // By default, our channel state will be unencrypted until we properly configure everything
+ dvmRadio.Status.Secure = false;
+ // Update softkey
+ int softkeyIdx = dvmRadio.Status.Softkeys.FindIndex(key => key.Name == SoftkeyName.SEC);
+ dvmRadio.Status.Softkeys[softkeyIdx].State = SoftkeyState.Off;
+
// Determine which TG we should be configuring for (scan TG or selected TG)
TalkgroupConfigObject tg = CurrentTalkgroup;
if (scanLandedTg != null)
@@ -611,26 +610,14 @@ public bool SetupChannelCrypto()
}
}
- // Update secure softkey & radio state
+ // If everything above succeeded and we're strapped or selected secure, update the softkey & state
if (tg.Strapped || Secure)
{
// Update status
dvmRadio.Status.Secure = true;
// Update softkey
- int keyIdx = dvmRadio.Status.Softkeys.FindIndex(key => key.Name == SoftkeyName.SEC);
- dvmRadio.Status.Softkeys[keyIdx].State = SoftkeyState.On;
+ dvmRadio.Status.Softkeys[softkeyIdx].State = SoftkeyState.On;
}
- else
- {
- // Update status
- dvmRadio.Status.Secure = false;
- // Update softkey
- int keyIdx = dvmRadio.Status.Softkeys.FindIndex(key => key.Name == SoftkeyName.SEC);
- dvmRadio.Status.Softkeys[keyIdx].State = SoftkeyState.Off;
- }
-
- // Send status update
- dvmRadio.StatusCallback();
// Return true if nothing failed
cryptoConfigured = true;
@@ -694,8 +681,6 @@ private void resetCall()
rxDataTimer.Stop();
// Reset P25 counter
p25N = 0;
- // Reset crypto configuration flag
- cryptoConfigured = false;
// Update status to idle if connected
if (Connected)
dvmRadio.Status.State = RadioState.Idle;
@@ -719,11 +704,14 @@ private void scanHangTimerCallback(Object source, ElapsedEventArgs e)
Log.Logger.Debug("({0:l}) Scan hang timer expiration, reverting to seleted talkgroup", Config.Name);
// Stop the hang timer
scanHangTimer.Stop();
+ // Reset the landed tg
+ scanLandedTg = null;
+ // Reset the crypto indicator appropriately
+ cryptoConfigured = false;
+ SetupChannelCrypto();
// Reset the channel text & update status
dvmRadio.Status.ChannelName = CurrentTalkgroup.Name;
dvmRadio.StatusCallback();
- // Reset the landed tg
- scanLandedTg = null;
}
///
@@ -1125,24 +1113,24 @@ public bool NuisanceDelete()
}
///
- /// Returns true if a call with the given peer/src/dst combo is present in our ignored calls list
+ /// Returns true if we're ignoring the given streamId
///
- /// P25DataReceviedEvent for the given call
+ /// stream ID for the given call
///
- private bool ignoringCall(P25DataReceivedEvent e)
+ private bool ignoringStream(uint streamId)
{
- return ignoredCalls.Any(call => call.PeerID == e.PeerId && call.SrcID == e.SrcId && call.DstID == e.DstId);
+ return ignoredStreams.Contains(streamId);
}
///
- /// Add a call with the given peer/src/dst to our ignored calls list
+ /// Add a call with the given streamId to our list of ignored streams
///
/// P25DataReceviedEvent for the given call
- private void ignoreCall(P25DataReceivedEvent e)
+ private void ignoreStream(uint streamId)
{
- if (!ignoringCall(e))
+ if (!ignoringStream(streamId))
{
- ignoredCalls.Add(new uniqueCall { PeerID = e.PeerId, SrcID = e.SrcId, DstID = e.DstId});
+ ignoredStreams.Add(streamId);
}
}
@@ -1150,11 +1138,11 @@ private void ignoreCall(P25DataReceivedEvent e)
/// Remove a call with the given peer/src/dst from our ignored calls list
///
///
- private void clearIgnored(P25DataReceivedEvent e)
+ private void clearIgnored(uint streamId)
{
- if (ignoringCall(e))
+ if (ignoringStream(streamId))
{
- ignoredCalls.Remove(new uniqueCall { PeerID = e.PeerId, SrcID = e.SrcId, DstID = e.DstId });
+ ignoredStreams.Remove(streamId);
}
}
}
From 8bac469382c8bca4b74ba990f0a99d2e9a4db8ca Mon Sep 17 00:00:00 2001
From: W3AXL <29879554+W3AXL@users.noreply.github.com>
Date: Sat, 23 May 2026 21:34:46 -0500
Subject: [PATCH 4/4] updated rc2-core commit
---
rc2-core | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rc2-core b/rc2-core
index 3c670f1..efc081d 160000
--- a/rc2-core
+++ b/rc2-core
@@ -1 +1 @@
-Subproject commit 3c670f1270a8cc63013a12dd99c56118af3069a5
+Subproject commit efc081dc41d2600b1885d3d08f4c1b278bc966d3