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 @@
+