Add support for KMMs; fix directory names to be uniform with the rest of the project

4.32j_maint
firealarmss 11 months ago
parent e627688b25
commit 50165cc03b

@ -172,6 +172,8 @@ namespace fnecore
public const byte NET_FUNC_PONG = 0x75; // Pong
public const byte NET_FUNC_GRANT = 0x7A; // Grant Request
public const byte NET_FUNC_KEY_REQ = 0x7C; // Encryption Key Request
public const byte NET_FUNC_KEY_RSP = 0x7D; // Encryption Key Response
public const byte NET_FUNC_ACK = 0x7E; // Packet Acknowledge
public const byte NET_FUNC_NAK = 0x7F; // Packet Negative Acknowledge

@ -19,6 +19,7 @@ using fnecore.DMR;
using fnecore.P25;
using fnecore.NXDN;
using fnecore.EDAC;
using fnecore.P25.kmm;
namespace fnecore
{
@ -530,6 +531,37 @@ namespace fnecore
this.Information = peer;
}
} // public class PeerConnectedEvent : EventArgs
/// <summary>
/// Event called when a kmm key response is received
/// </summary>
public class KeyResponseEvent : EventArgs
{
/// <summary>
/// KMM Message Id
/// </summary>
public byte MessageId { get; set; }
/// <summary>
/// <see cref="KmmModifyKey"/> instance
/// </summary>
public KmmModifyKey KmmKey { get; set; }
/// <summary>
/// Raw Data
/// </summary>
public byte[] Data { get; set; }
/*
** Methods
*/
public KeyResponseEvent(byte messageId, KmmModifyKey kmmKey, byte[] data) : base()
{
this.MessageId = messageId;
this.KmmKey = kmmKey;
this.Data = data;
}
}
/// <summary>
/// This class implements some base functionality for all other FNE network classes.
@ -638,6 +670,11 @@ namespace fnecore
/// </summary>
public Action<uint> PeerDisconnected = null;
/// <summary>
/// Event action thats called when a key response is received
/// </summary>
public event EventHandler<KeyResponseEvent> KeyResponse;
/// <summary>
/// Callback action that handles internal logging.
/// </summary>
@ -831,6 +868,7 @@ namespace fnecore
Buffer.BlockCopy(message, 0, buffer, (int)(Constants.RtpHeaderLengthBytes + Constants.RtpExtensionHeaderLengthBytes + Constants.RtpFNEHeaderLengthBytes),
message.Length);
return buffer;
}
@ -883,5 +921,15 @@ namespace fnecore
if (PeerConnected != null)
PeerConnected.Invoke(this, e);
}
/// <summary>
/// Helper to fire the key response event
/// </summary>
/// <param name="e"></param>
protected void FireKeyResponse(KeyResponseEvent e)
{
if (KeyResponse != null)
KeyResponse.Invoke(this, e);
}
} // public abstract class FneBase
} // namespace fnecore

@ -27,6 +27,7 @@ using fnecore.NXDN;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Linq;
using fnecore.P25.kmm;
namespace fnecore
{
@ -343,6 +344,40 @@ namespace fnecore
SendMaster(CreateOpcode(Constants.NET_FUNC_ANNOUNCE, Constants.NET_ANNC_SUBFUNC_UNIT_DEREG), res, 0, 0, true);
}
/// <summary>
/// Helper to send a key request to the master
/// </summary>
/// <param name="algId"></param>
/// <param name="kId"></param>
public void SendMasterKeyRequest(byte algId, ushort kId)
{
byte[] res = new byte[32];
KmmModifyKey modifyKey = new KmmModifyKey
{
AlgId = algId,
KeyId = kId
};
KeysetItem ks = new KeysetItem
{
KeysetId = 0,
AlgId = algId,
KeyLength = 32
};
modifyKey.KeysetItem = ks;
modifyKey.MessageLength = 21;
byte[] payload = new byte[modifyKey.MessageLength];
modifyKey.Encode(payload);
Array.Copy(payload, 0, res, 11, payload.Length);
SendMaster(CreateOpcode(Constants.NET_FUNC_KEY_REQ, Constants.NET_SUBFUNC_NOP), res, Constants.RtpCallEndSeq, 0, true);
}
/// <summary>
/// Helper to update the RTP packet sequence.
/// </summary>
@ -673,6 +708,26 @@ namespace fnecore
}
}
break;
case Constants.NET_FUNC_KEY_RSP: // Key Response
if (this.peerId == peerId)
{
byte[] payload = message.Skip(11).ToArray();
byte messageId = payload[0];
if (messageId == (byte)KmmMessageType.MODIFY_KEY_CMD)
{
KmmModifyKey modifyKey = new KmmModifyKey();
modifyKey.Decode(payload);
FireKeyResponse(new KeyResponseEvent(messageId, modifyKey, message));
}
else
{
Log(LogLevel.WARNING, $"Unknown KEY_RSP Message ID: {messageId}");
}
}
break;
default:
Log(LogLevel.ERROR, $"({systemName}) Unknown opcode {FneUtils.BytesToString(message, 0, 4)} -- {FneUtils.HexDump(message, 0)}");

@ -205,6 +205,8 @@ namespace fnecore
this.fne.PeerIgnored = PeerIgnored;
this.fne.PeerConnected += PeerConnected;
this.fne.KeyResponse += KeyResponse;
// hook logger callback
this.fne.LogLevel = fneLogLevel;
}
@ -319,6 +321,13 @@ namespace fnecore
/// <param name="e"></param>
protected abstract void PeerConnected(object sender, PeerConnectedEvent e);
/// <summary>
/// Event handler used to handle a key response
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected abstract void KeyResponse(object sender, KeyResponseEvent e);
/// <summary>
/// Creates an DMR frame message.
/// </summary>

@ -0,0 +1,127 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - Fixed Network Equipment Core Library
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Fixed Network Equipment Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
namespace fnecore.P25.kmm
{
/// <summary>
/// Enc. Key Item
/// </summary>
public class KeyItem
{
private const int MAX_ENC_KEY_LENGTH = 32;
private readonly byte[] _keyMaterial = new byte[MAX_ENC_KEY_LENGTH];
public byte KeyFormat { get; set; } = 0x80;
public ushort Sln { get; set; }
public ushort KeyId { get; set; }
public uint KeyLength { get; private set; }
/// <summary>
/// Creates an instance of <see cref="KeyItem"/>
/// </summary>
public KeyItem() { /* stub */ }
/// <summary>
/// Creates an instance of <see cref="KeyItem"/>
/// </summary>
/// <param name="other"></param>
public KeyItem(KeyItem other)
{
KeyFormat = other.KeyFormat;
Sln = other.Sln;
KeyId = other.KeyId;
SetKey(other._keyMaterial, other.KeyLength);
}
/// <summary>
/// Set Enc. Key
/// </summary>
/// <param name="key"></param>
/// <param name="keyLength"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public void SetKey(byte[] key, uint keyLength)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (keyLength > MAX_ENC_KEY_LENGTH) throw new ArgumentOutOfRangeException(nameof(keyLength));
KeyLength = keyLength;
Array.Clear(_keyMaterial, 0, MAX_ENC_KEY_LENGTH);
Array.Copy(key, _keyMaterial, keyLength);
}
/// <summary>
/// Return the Enc. Key
/// </summary>
/// <returns>Enc. Key</returns>
public byte[] GetKey()
{
return _keyMaterial.Take((int)KeyLength).ToArray();
}
} // public class KeyItem
/// <summary>
/// Keyset item
/// </summary>
public class KeysetItem
{
public byte KeysetId { get; set; }
public byte AlgId { get; set; }
public byte KeyLength { get; set; }
public List<KeyItem> Keys { get; private set; } = new List<KeyItem>();
/// <summary>
/// Creates an instance of <see cref="KeysetItem"/>
/// </summary>
public KeysetItem() { /* stub */ }
/// <summary>
/// Creates an instance of <see cref="KeysetItem"/>
/// </summary>
/// <param name="other"></param>
public KeysetItem(KeysetItem other)
{
KeysetId = other.KeysetId;
AlgId = other.AlgId;
KeyLength = other.KeyLength;
Keys = other.Keys.Select(k => new KeyItem(k)).ToList();
}
/// <summary>
/// Keyset length
/// </summary>
public uint Length => (uint)(4 + Keys.Count * (5 + KeyLength));
/// <summary>
/// Add <see cref="KeyItem"/> to the Keys list
/// </summary>
/// <param name="key"></param>
public void AddKey(KeyItem key)
{
Keys.Add(key);
}
/// <summary>
/// Overwrite Keys list
/// </summary>
/// <param name="keys"></param>
public void SetKeys(IEnumerable<KeyItem> keys)
{
Keys = keys.ToList();
}
} // public class KeysetItem
} // namespace fnecore.P25.kmm

@ -0,0 +1,76 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - Fixed Network Equipment Core Library
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Fixed Network Equipment Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
using System;
namespace fnecore.P25.kmm
{
/// <summary>
/// KMM Frame base class
/// </summary>
public class KmmFrame
{
public byte MessageId { get; set; }
public ushort MessageLength { get; set; }
public byte RespKind { get; private set; } = 0;
public bool Complete { get; private set; } = true;
public ushort DstLlId { get; private set; }
public ushort SrcLlId { get; private set; }
/// <summary>
/// Creates an instance of <see cref="KmmFrame"/>
/// </summary>
public KmmFrame() { /* sub */ }
/// <summary>
/// KMM Frame length
/// </summary>
public virtual ushort Length => P25Defines.KMM_FRAME_LENGTH;
/// <summary>
/// Encode KMM Frame Header
/// </summary>
/// <param name="data"></param>
/// <exception cref="ArgumentException"></exception>
protected void EncodeHeader(byte[] data)
{
if (data.Length < 8)
throw new ArgumentException("Data buffer too small");
data[0] = MessageId;
FneUtils.WriteBytes(Length, ref data, 1);
data[3] = (byte)((RespKind << 6) | (Complete ? 0x00 : 0x01));
FneUtils.WriteBytes(DstLlId, ref data, 4);
FneUtils.WriteBytes(SrcLlId, ref data, 6);
}
/// <summary>
/// Decode KMM Frame Header
/// </summary>
/// <param name="data"></param>
/// <exception cref="ArgumentException"></exception>
protected void DecodeHeader(byte[] data)
{
if (data.Length < 8)
throw new ArgumentException("Data buffer too small");
MessageId = data[0];
MessageLength = FneUtils.ToUInt16(data, 1);
RespKind = (byte)((data[3] >> 6) & 0x03);
Complete = (data[3] & 0x01) == 0;
DstLlId = FneUtils.ToUInt16(data, 4);
SrcLlId = FneUtils.ToUInt16(data, 6);
}
} // public class KmmFrame
} // namespace fnecore.P25.kmm

@ -0,0 +1,24 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - Fixed Network Equipment Core Library
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Fixed Network Equipment Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
namespace fnecore.P25.kmm
{
/// <summary>
/// KMM Message Type
/// </summary>
public enum KmmMessageType
{
NULL_CMD = 0x00,
MODIFY_KEY_CMD = 0x13
}
} // namespace fnecore.P25.kmm

@ -0,0 +1,126 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - Fixed Network Equipment Core Library
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Fixed Network Equipment Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
using System;
using System.Linq;
namespace fnecore.P25.kmm
{
/// <summary>
/// KMM Modify Key
/// </summary>
public class KmmModifyKey : KmmFrame
{
private byte[] _mi;
public const int KMM_MODIFY_KEY_LENGTH = 21;
public byte DecryptInfoFmt { get; set; } = P25Defines.KMM_DECRYPT_INSTRUCTION_NONE;
public byte AlgId { get; set; }
public ushort KeyId { get; set; }
public KeysetItem KeysetItem { get; set; } = new KeysetItem();
/// <summary>
/// Creates an instance <see cref="KmmModifyKey"/>
/// </summary>
public KmmModifyKey()
{
MessageId = (byte)KmmMessageType.MODIFY_KEY_CMD;
_mi = new byte[P25Defines.P25_MI_LENGTH];
}
/// <summary>
/// KMM Frame Length
/// </summary>
public override ushort Length => 21;
/// <summary>
/// Encode a KMM Modify Key
/// </summary>
/// <param name="data"></param>
public void Encode(byte[] data)
{
EncodeHeader(data);
data[10] = DecryptInfoFmt;
data[11] = AlgId;
FneUtils.WriteBytes(KeyId, ref data, 12);
int offset = 14;
if (DecryptInfoFmt == P25Defines.KMM_DECRYPT_INSTRUCTION_MI)
{
Array.Copy(_mi, 0, data, offset, P25Defines.P25_MI_LENGTH);
offset += P25Defines.P25_MI_LENGTH;
}
data[offset] = KeysetItem.KeysetId;
data[offset + 1] = KeysetItem.AlgId;
data[offset + 2] = KeysetItem.KeyLength;
data[offset + 3] = (byte)KeysetItem.Keys.Count;
offset += 4;
foreach (var key in KeysetItem.Keys)
{
int keyNameLen = key.KeyFormat & 0x1F;
data[offset] = (byte)(key.KeyFormat | keyNameLen);
FneUtils.WriteBytes(key.Sln, ref data, offset + 1);
FneUtils.WriteBytes(key.KeyId, ref data, offset + 3);
key.GetKey().CopyTo(data, offset + 5 + keyNameLen);
offset += 5 + keyNameLen + KeysetItem.KeyLength;
}
}
/// <summary>
/// Decode a KMM Modify Key
/// </summary>
/// <param name="data"></param>
public void Decode(byte[] data)
{
DecodeHeader(data);
DecryptInfoFmt = data[10];
AlgId = data[11];
KeyId = FneUtils.ToUInt16(data, 12);
int offset = 14;
if (DecryptInfoFmt == P25Defines.KMM_DECRYPT_INSTRUCTION_MI)
{
Array.Copy(data, offset, _mi, 0, P25Defines.P25_MI_LENGTH);
offset += P25Defines.P25_MI_LENGTH;
}
KeysetItem.KeysetId = data[offset];
KeysetItem.AlgId = data[offset + 1];
KeysetItem.KeyLength = data[offset + 2];
int keyCount = data[offset + 3];
offset += 4;
KeysetItem.Keys.Clear();
for (int i = 0; i < keyCount; i++)
{
KeyItem key = new KeyItem();
int keyNameLen = data[offset] & 0x1F;
key.KeyFormat = data[offset];
key.Sln = FneUtils.ToUInt16(data, offset + 1);
key.KeyId = FneUtils.ToUInt16(data, offset + 3);
key.SetKey(
data.Skip(offset + 5 + keyNameLen).Take((int)KeysetItem.KeyLength).ToArray(),
KeysetItem.KeyLength
);
offset += 5 + keyNameLen + KeysetItem.KeyLength;
KeysetItem.Keys.Add(key);
}
}
} // public class KmmModifyKey
} // namespace fnecore.P25.kmm

@ -0,0 +1,25 @@
// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - Fixed Network Equipment Core Library
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Fixed Network Equipment Core Library
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2025 Caleb, K4PHP
*
*/
namespace fnecore.P25.kmm
{
/// <summary>
/// KMM Response Kind
/// </summary>
public enum KmmResponseKind
{
NONE = 0x00,
DELAYED = 0x01,
IMMEDIATE = 0x02
}
} // namespace fnecore.P25.kmm

@ -133,9 +133,17 @@ namespace fnecore.P25
public const byte P25_MFG_STANDARD = 0x00;
public const byte P25_ALGO_UNENCRYPT = 0x80;
public const byte P25_ALGO_DES = 0x81;
public const byte P25_ALGO_AES = 0x84;
public const byte P25_ALGO_ARC4 = 0xAA;
public const byte KMM_DECRYPT_INSTRUCTION_NONE = 0x00;
public const byte KMM_DECRYPT_INSTRUCTION_MI = 0x40;
public const byte P25_MI_LENGTH = 9;
public const byte KMM_FRAME_LENGTH = 9;
public const byte P25_TSDU_FRAME_LENGTH_BYTES = 45;
public const byte P25_LDU_FRAME_LENGTH_BYTES = 216;

Loading…
Cancel
Save

Powered by TurnKey Linux.