From 7fda7dc2c74241f543187a8f742d40cc3a9f7b6d Mon Sep 17 00:00:00 2001 From: firealarmss Date: Sat, 8 Mar 2025 22:51:00 -0600 Subject: [PATCH] Add support for P25 encryption; several fixes and features --- WhackerLinkConsoleV2.sln | 12 +- WhackerLinkConsoleV2/AliasTools.cs | 55 +++++ WhackerLinkConsoleV2/ChannelBox.xaml.cs | 7 +- WhackerLinkConsoleV2/MBEToneDetector.cs | 125 +++++++++++ WhackerLinkConsoleV2/MainWindow.xaml | 17 +- WhackerLinkConsoleV2/MainWindow.xaml.cs | 207 +++++++++++++----- WhackerLinkConsoleV2/P25Crypto.cs | 122 ++++++++--- WhackerLinkConsoleV2/SystemStatusBox.xaml.cs | 1 + .../WhackerLinkConsoleV2.csproj | 1 + WhackerLinkConsoleV2/codeplugs/codeplug.yml | 3 + WhackerLinkLib | 2 +- fnecore | 2 +- 12 files changed, 460 insertions(+), 94 deletions(-) create mode 100644 WhackerLinkConsoleV2/AliasTools.cs create mode 100644 WhackerLinkConsoleV2/MBEToneDetector.cs diff --git a/WhackerLinkConsoleV2.sln b/WhackerLinkConsoleV2.sln index b14cad8..ad8965d 100644 --- a/WhackerLinkConsoleV2.sln +++ b/WhackerLinkConsoleV2.sln @@ -70,8 +70,8 @@ Global {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Release|Any CPU.Build.0 = Release|Any CPU {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Release|x64.ActiveCfg = Release|x64 {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Release|x64.Build.0 = Release|x64 - {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Release|x86.ActiveCfg = Release|Any CPU - {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Release|x86.Build.0 = Release|Any CPU + {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Release|x86.ActiveCfg = Release|x86 + {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.Release|x86.Build.0 = Release|x86 {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.WIN32|Any CPU.ActiveCfg = WIN32|Any CPU {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.WIN32|Any CPU.Build.0 = WIN32|Any CPU {710D1FA8-2E0D-42CB-9174-FCD9EB7A718F}.WIN32|x64.ActiveCfg = WIN32|x64 @@ -98,10 +98,10 @@ Global {1F06ECB1-9928-1430-63F4-2E01522A0510}.Release|x86.Build.0 = Release|Any CPU {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|Any CPU.ActiveCfg = WIN32|Any CPU {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|Any CPU.Build.0 = WIN32|Any CPU - {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|x64.ActiveCfg = WIN32|Any CPU - {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|x64.Build.0 = WIN32|Any CPU - {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|x86.ActiveCfg = WIN32|Any CPU - {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|x86.Build.0 = WIN32|Any CPU + {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|x64.ActiveCfg = Release|Any CPU + {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|x64.Build.0 = Release|Any CPU + {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|x86.ActiveCfg = Release|Any CPU + {1F06ECB1-9928-1430-63F4-2E01522A0510}.WIN32|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WhackerLinkConsoleV2/AliasTools.cs b/WhackerLinkConsoleV2/AliasTools.cs new file mode 100644 index 0000000..25584f8 --- /dev/null +++ b/WhackerLinkConsoleV2/AliasTools.cs @@ -0,0 +1,55 @@ +/* +* WhackerLink - WhackerLinkConsoleV2 +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Copyright (C) 2025 Caleb, K4PHP +* +*/ + +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using WhackerLinkLib.Models; +using YamlDotNet.Serialization.NamingConventions; +using YamlDotNet.Serialization; +using System.Diagnostics; + +namespace WhackerLinkConsoleV2 +{ + public static class AliasTools + { + public static List LoadAliases(string filePath) + { + if (!File.Exists(filePath)) + { + throw new FileNotFoundException("Alias file not found.", filePath); + } + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + var yamlText = File.ReadAllText(filePath); + return deserializer.Deserialize>(yamlText); + } + + public static string GetAliasByRid(List aliases, int rid) + { + var match = aliases.FirstOrDefault(a => a.Rid == rid); + return match?.Alias; + } + } +} diff --git a/WhackerLinkConsoleV2/ChannelBox.xaml.cs b/WhackerLinkConsoleV2/ChannelBox.xaml.cs index b805066..b57659f 100644 --- a/WhackerLinkConsoleV2/ChannelBox.xaml.cs +++ b/WhackerLinkConsoleV2/ChannelBox.xaml.cs @@ -75,9 +75,12 @@ namespace WhackerLinkConsoleV2.Controls public MBEEncoder encoder; public MBEDecoder decoder; + public MBEToneDetector toneDetector = new MBEToneDetector(); + public P25Crypto crypter = new P25Crypto(); public bool IsReceiving { get; set; } = false; + public bool IsReceivingEncrypted { get; set; } = false; public string LastSrcId { @@ -221,10 +224,6 @@ namespace WhackerLinkConsoleV2.Controls PageSelectButton.IsEnabled = false; ChannelMarkerBtn.IsEnabled = false; } - - byte[] key = { 00, 00, 00, 00, 00 }; - - crypter.AddKey(00, 0xaa, key); } private void ChannelBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) diff --git a/WhackerLinkConsoleV2/MBEToneDetector.cs b/WhackerLinkConsoleV2/MBEToneDetector.cs new file mode 100644 index 0000000..2b511aa --- /dev/null +++ b/WhackerLinkConsoleV2/MBEToneDetector.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NWaves.Signals; +using NWaves.Transforms; + +namespace WhackerLinkConsoleV2 +{ + public class MBEToneDetector + { + // Samplerate is 8000 Hz + private static int sample_rate = 8000; + + // We operate on 160 sample (20ms @ 8kHz) windows + private static int window_size = 160; + + // There are 128 possible tone indexes per TIA-102.BABA-1 + private static int num_coeffs = 128; + + // Bin size in hz + private static float bin_size_hz = (float)sample_rate / 2f / (float)num_coeffs; + + // Bounds of tone detection (in bin index format) + private int low_bin_limit; + private int high_bin_limit; + + // This is the tone detection ratio (amplitude of max bin divided by average of all others) + private int detect_ratio; + + // This is the number of "hits" on a frequency we need to get before we detect a valid tone + private int hits_reqd; + + // Counter for the above hits + private int hits_freq; + private int num_hits; + + // The STFT (short-time fourier transform) operator + private Stft stft; + + /// + /// Create a pitch detector which reports the running average of pitch for a sequence of samples + /// + /// Ratio required for a valid tone detection + /// Number of repeated "hits" on a frequency to count as a tone detection + public MBEToneDetector(int detect_ratio = 90, int hits_reqd = 2, int low_limit = 250, int high_limit = 3000) + { + this.detect_ratio = detect_ratio; + this.hits_reqd = hits_reqd; + stft = new Stft(window_size, 1, NWaves.Windows.WindowType.Hann, num_coeffs); + hits_freq = 0; + num_hits = 0; + low_bin_limit = (int)(low_limit / bin_size_hz); + high_bin_limit = (int)(high_limit / bin_size_hz); + } + + /// + /// Perform a tone analysis on the provided samples, and return a tone frequency if one is detected + /// + /// + /// + public int Detect(DiscreteSignal signal) + { + // Validate input + if (signal.Length != window_size) + { + throw new ArgumentOutOfRangeException($"Signal must be {window_size} samples long!"); + } + if (signal.SamplingRate != sample_rate) + { + throw new ArgumentOutOfRangeException($"Signal must have sample rate of {sample_rate} Hz!"); + } + + // Analyze + float[] values = stft.Spectrogram(signal)[0]; + + // Remove bins outside our limit + float[] limited_values = values[low_bin_limit..high_bin_limit]; + + // Find max (from https://stackoverflow.com/a/50239922/1842613) + (float max_val, int max_idx) = limited_values.Select((n, i) => (n, i)).Max(); + + // Add back in our lower limit so the index is correct + max_idx += low_bin_limit; + + // Calculate sum of all others + float sum = values.Sum() - max_val; + + // Find average + float avg = sum / (window_size - 1); + + // Find ratio + float ratio = max_val / avg; + + // Debug + //Log.Logger.Debug($"(Tone detector): max at {max_idx} ({(int)(max_idx * bin_size_hz)} Hz): {max_val}, ratio: {ratio}"); + + // Return if above threshold + if (ratio > detect_ratio) + { + // Calculate the tone frequency + int tone_freq = (int)(bin_size_hz * max_idx); + + // Determine hits + if (hits_freq == tone_freq) + { + num_hits++; + if (num_hits >= hits_reqd) + { + // Debug + //Log.Logger.Debug($"Detected {tone_freq} Hz tone! (ratio {ratio})"); + return tone_freq; + } + } + else + { + num_hits = 1; + hits_freq = tone_freq; + } + } + return 0; + } + } +} diff --git a/WhackerLinkConsoleV2/MainWindow.xaml b/WhackerLinkConsoleV2/MainWindow.xaml index 302a8c7..ef32e73 100644 --- a/WhackerLinkConsoleV2/MainWindow.xaml +++ b/WhackerLinkConsoleV2/MainWindow.xaml @@ -11,7 +11,7 @@ - + @@ -42,7 +42,7 @@ - + @@ -162,6 +162,19 @@ +