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