diff --git a/WhackerLinkConsoleV2/CallHistoryWindow.xaml b/WhackerLinkConsoleV2/CallHistoryWindow.xaml new file mode 100644 index 0000000..5c3b10e --- /dev/null +++ b/WhackerLinkConsoleV2/CallHistoryWindow.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/WhackerLinkConsoleV2/CallHistoryWindow.xaml.cs b/WhackerLinkConsoleV2/CallHistoryWindow.xaml.cs new file mode 100644 index 0000000..c8ea69d --- /dev/null +++ b/WhackerLinkConsoleV2/CallHistoryWindow.xaml.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace WhackerLinkConsoleV2 +{ + public partial class CallHistoryWindow : Window + { + public CallHistoryViewModel ViewModel { get; set; } + + public CallHistoryWindow() + { + InitializeComponent(); + ViewModel = new CallHistoryViewModel(); + DataContext = ViewModel; + } + + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) + { + e.Cancel = true; + this.Hide(); + } + + public void AddCall(string channel, int srcId, int dstId) + { + Dispatcher.Invoke(() => + { + ViewModel.CallHistory.Insert(0, new CallEntry + { + Channel = channel, + SrcId = srcId, + DstId = dstId, + BackgroundColor = Brushes.Transparent + }); + }); + } + + public void ChannelKeyed(string channel, int srcId, bool encrypted) + { + Dispatcher.Invoke(() => + { + foreach (var entry in ViewModel.CallHistory.Where(c => c.Channel == channel && c.SrcId == srcId)) + { + if (!encrypted) + entry.BackgroundColor = Brushes.LightGreen; + else + entry.BackgroundColor = Brushes.Orange; + + } + }); + } + + public void ChannelUnkeyed(string channel, int srcId) + { + Dispatcher.Invoke(() => + { + foreach (var entry in ViewModel.CallHistory.Where(c => c.Channel == channel && c.SrcId == srcId)) + { + entry.BackgroundColor = Brushes.Transparent; + } + }); + } + } + + public class CallHistoryViewModel + { + public ObservableCollection CallHistory { get; set; } + + public CallHistoryViewModel() + { + CallHistory = new ObservableCollection(); + } + } + + public class CallEntry : DependencyObject + { + public string Channel { get; set; } + public int SrcId { get; set; } + public int DstId { get; set; } + + public static readonly DependencyProperty BackgroundColorProperty = + DependencyProperty.Register(nameof(BackgroundColor), typeof(Brush), typeof(CallEntry), new PropertyMetadata(Brushes.Transparent)); + + public Brush BackgroundColor + { + get { return (Brush)GetValue(BackgroundColorProperty); } + set { SetValue(BackgroundColorProperty, value); } + } + } +} diff --git a/WhackerLinkConsoleV2/FneSystemBase.P25.cs b/WhackerLinkConsoleV2/FneSystemBase.P25.cs index 34eafbb..e2ab506 100644 --- a/WhackerLinkConsoleV2/FneSystemBase.P25.cs +++ b/WhackerLinkConsoleV2/FneSystemBase.P25.cs @@ -23,6 +23,8 @@ using fnecore.P25; using NAudio.Wave; using System.Windows.Threading; +using System.Security.Cryptography; +using System.Security.Cryptography.Xml; namespace WhackerLinkConsoleV2 { @@ -64,9 +66,19 @@ namespace WhackerLinkConsoleV2 return; } - public void CreateNewP25MessageHdr(byte duid, RemoteCallData callData, ref byte[] data) + public void CreateNewP25MessageHdr(byte duid, RemoteCallData callData, ref byte[] data, byte algId = 0, ushort kId = 0, byte[] mi = null) { CreateP25MessageHdr(duid, callData, ref data); + + // if an mi is present, this is an encrypted header + if (mi != null) + { + data[14U] |= 0x08; // Control bit + + data[181U] = algId; // Algorithm ID + FneUtils.WriteBytes(kId, ref data, 182); // Key ID + Array.Copy(mi, 0, data, 184, P25Defines.P25_MI_LENGTH); // Message Indicator + } } /// @@ -272,7 +284,7 @@ namespace WhackerLinkConsoleV2 /// /// /// - private void EncodeLDU2(ref byte[] data, int offset, byte[] imbe, byte frameType) + private void EncodeLDU2(ref byte[] data, int offset, byte[] imbe, byte frameType, CryptoParams cryptoParams) { if (data == null) throw new ArgumentNullException("data"); @@ -328,33 +340,33 @@ namespace WhackerLinkConsoleV2 break; case P25DFSI.P25_DFSI_LDU2_VOICE12: { - dfsiFrame[1U] = 0; // Message Indicator - dfsiFrame[2U] = 0; - dfsiFrame[3U] = 0; + dfsiFrame[1U] = cryptoParams.Mi[0]; // Message Indicator + dfsiFrame[2U] = cryptoParams.Mi[1]; + dfsiFrame[3U] = cryptoParams.Mi[2]; Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU2_VOICE13: { - dfsiFrame[1U] = 0; // Message Indicator - dfsiFrame[2U] = 0; - dfsiFrame[3U] = 0; + dfsiFrame[1U] = cryptoParams.Mi[3]; // Message Indicator + dfsiFrame[2U] = cryptoParams.Mi[4]; + dfsiFrame[3U] = cryptoParams.Mi[5]; Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU2_VOICE14: { - dfsiFrame[1U] = 0; // Message Indicator - dfsiFrame[2U] = 0; - dfsiFrame[3U] = 0; + dfsiFrame[1U] = cryptoParams.Mi[6]; // Message Indicator + dfsiFrame[2U] = cryptoParams.Mi[7]; + dfsiFrame[3U] = cryptoParams.Mi[8]; Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU2_VOICE15: { - dfsiFrame[1U] = P25Defines.P25_ALGO_UNENCRYPT; // Algorithm ID - dfsiFrame[2U] = 0; // Key ID - dfsiFrame[3U] = 0; + dfsiFrame[1U] = cryptoParams.AlgId; // Algorithm ID + FneUtils.WriteBytes(cryptoParams.KeyId, ref dfsiFrame, 2); // Key ID + //dfsiFrame[3U] = 0; Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; @@ -399,46 +411,49 @@ namespace WhackerLinkConsoleV2 /// /// Input LDU data array /// Output data array - public void CreateP25LDU2Message(in byte[] netLDU2, ref byte[] data) + public void CreateP25LDU2Message(in byte[] netLDU2, ref byte[] data, CryptoParams cryptoParams = null) { + if (cryptoParams == null) + cryptoParams = new CryptoParams(); + // pack DFSI data int count = P25_MSG_HDR_SIZE; byte[] imbe = new byte[IMBE_BUF_LEN]; Buffer.BlockCopy(netLDU2, 10, imbe, 0, IMBE_BUF_LEN); - EncodeLDU2(ref data, 24, imbe, P25DFSI.P25_DFSI_LDU2_VOICE10); + EncodeLDU2(ref data, 24, imbe, P25DFSI.P25_DFSI_LDU2_VOICE10, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU2, 26, imbe, 0, IMBE_BUF_LEN); - EncodeLDU2(ref data, 46, imbe, P25DFSI.P25_DFSI_LDU2_VOICE11); + EncodeLDU2(ref data, 46, imbe, P25DFSI.P25_DFSI_LDU2_VOICE11, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE11_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU2, 55, imbe, 0, IMBE_BUF_LEN); - EncodeLDU2(ref data, 60, imbe, P25DFSI.P25_DFSI_LDU2_VOICE12); + EncodeLDU2(ref data, 60, imbe, P25DFSI.P25_DFSI_LDU2_VOICE12, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE12_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU2, 80, imbe, 0, IMBE_BUF_LEN); - EncodeLDU2(ref data, 77, imbe, P25DFSI.P25_DFSI_LDU2_VOICE13); + EncodeLDU2(ref data, 77, imbe, P25DFSI.P25_DFSI_LDU2_VOICE13, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE13_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU2, 105, imbe, 0, IMBE_BUF_LEN); - EncodeLDU2(ref data, 94, imbe, P25DFSI.P25_DFSI_LDU2_VOICE14); + EncodeLDU2(ref data, 94, imbe, P25DFSI.P25_DFSI_LDU2_VOICE14, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE14_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU2, 130, imbe, 0, IMBE_BUF_LEN); - EncodeLDU2(ref data, 111, imbe, P25DFSI.P25_DFSI_LDU2_VOICE15); + EncodeLDU2(ref data, 111, imbe, P25DFSI.P25_DFSI_LDU2_VOICE15, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE15_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU2, 155, imbe, 0, IMBE_BUF_LEN); - EncodeLDU2(ref data, 128, imbe, P25DFSI.P25_DFSI_LDU2_VOICE16); + EncodeLDU2(ref data, 128, imbe, P25DFSI.P25_DFSI_LDU2_VOICE16, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE16_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU2, 180, imbe, 0, IMBE_BUF_LEN); - EncodeLDU2(ref data, 145, imbe, P25DFSI.P25_DFSI_LDU2_VOICE17); + EncodeLDU2(ref data, 145, imbe, P25DFSI.P25_DFSI_LDU2_VOICE17, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE17_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU2, 204, imbe, 0, IMBE_BUF_LEN); - EncodeLDU2(ref data, 162, imbe, P25DFSI.P25_DFSI_LDU2_VOICE18); + EncodeLDU2(ref data, 162, imbe, P25DFSI.P25_DFSI_LDU2_VOICE18, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES; data[23U] = (byte)count; @@ -465,4 +480,14 @@ namespace WhackerLinkConsoleV2 } } } // public abstract partial class FneSystemBase : fnecore.FneSystemBase + + /// + /// + /// + public class CryptoParams + { + public byte[] Mi { get; set; } = new byte[P25Defines.P25_MI_LENGTH]; + public byte AlgId { get; set; } = P25Defines.P25_ALGO_UNENCRYPT; + public ushort KeyId { get; set; } + } } \ No newline at end of file diff --git a/WhackerLinkConsoleV2/MainWindow.xaml b/WhackerLinkConsoleV2/MainWindow.xaml index 84f228f..3cc66f8 100644 --- a/WhackerLinkConsoleV2/MainWindow.xaml +++ b/WhackerLinkConsoleV2/MainWindow.xaml @@ -188,6 +188,19 @@ +