// SPDX-License-Identifier: AGPL-3.0-only /** * Digital Voice Modem - Audio Bridge * AGPLv3 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * @package DVM / Audio Bridge * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0) * * Copyright (C) 2022-2024 Bryan Biedenkapp, N2PLL * Copyright (C) 2025 Caleb, K4PHP * */ using fnecore; using fnecore.P25; namespace DVMConsole { /// /// Implements a FNE system base. /// public abstract partial class FneSystemBase : fnecore.FneSystemBase { public const int IMBE_BUF_LEN = 11; /* ** Methods */ /// /// Callback used to validate incoming P25 data. /// /// Peer ID /// Source Address /// Destination Address /// Call Type (Group or Private) /// P25 DUID /// Frame Type /// Stream ID /// Raw message data /// True, if data stream is valid, otherwise false. protected override bool P25DataValidate(uint peerId, uint srcId, uint dstId, CallType callType, P25DUID duid, FrameType frameType, uint streamId, byte[] message) { return true; } /// /// Event handler used to pre-process incoming P25 data. /// /// /// protected override void P25DataPreprocess(object sender, P25DataReceivedEvent e) { return; } 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 } } /// /// Helper to send a P25 TDU message. /// /// public void SendP25TDU(uint srcId, uint dstId, bool grantDemand = false) { RemoteCallData callData = new RemoteCallData() { SrcId = srcId, DstId = dstId, LCO = P25Defines.LC_GROUP }; SendP25TDU(callData, grantDemand); } /// /// Encode a logical link data unit 1. /// /// /// /// /// /// private void EncodeLDU1(ref byte[] data, int offset, byte[] imbe, byte frameType, uint srcId, uint dstId) { if (data == null) throw new ArgumentNullException("data"); if (imbe == null) throw new ArgumentNullException("imbe"); // determine the LDU1 DFSI frame length, its variable uint frameLength = P25DFSI.P25_DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES; switch (frameType) { case P25DFSI.P25_DFSI_LDU1_VOICE1: frameLength = P25DFSI.P25_DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU1_VOICE2: frameLength = P25DFSI.P25_DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU1_VOICE3: frameLength = P25DFSI.P25_DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU1_VOICE4: frameLength = P25DFSI.P25_DFSI_LDU1_VOICE4_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU1_VOICE5: frameLength = P25DFSI.P25_DFSI_LDU1_VOICE5_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU1_VOICE6: frameLength = P25DFSI.P25_DFSI_LDU1_VOICE6_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU1_VOICE7: frameLength = P25DFSI.P25_DFSI_LDU1_VOICE7_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU1_VOICE8: frameLength = P25DFSI.P25_DFSI_LDU1_VOICE8_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU1_VOICE9: frameLength = P25DFSI.P25_DFSI_LDU1_VOICE9_FRAME_LENGTH_BYTES; break; default: return; } byte[] dfsiFrame = new byte[frameLength]; dfsiFrame[0U] = frameType; // Frame Type // different frame types mean different things switch (frameType) { case P25DFSI.P25_DFSI_LDU1_VOICE2: { Buffer.BlockCopy(imbe, 0, dfsiFrame, 1, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU1_VOICE3: { dfsiFrame[1U] = P25Defines.LC_GROUP; // LCO dfsiFrame[2U] = 0; // MFId dfsiFrame[3U] = 0; // Service Options Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU1_VOICE4: { dfsiFrame[1U] = (byte)((dstId >> 16) & 0xFFU); // Talkgroup Address dfsiFrame[2U] = (byte)((dstId >> 8) & 0xFFU); dfsiFrame[3U] = (byte)((dstId >> 0) & 0xFFU); Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU1_VOICE5: { dfsiFrame[1U] = (byte)((srcId >> 16) & 0xFFU); // Source Address dfsiFrame[2U] = (byte)((srcId >> 8) & 0xFFU); dfsiFrame[3U] = (byte)((srcId >> 0) & 0xFFU); Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU1_VOICE6: { dfsiFrame[1U] = 0; // RS (24,12,13) dfsiFrame[2U] = 0; // RS (24,12,13) dfsiFrame[3U] = 0; // RS (24,12,13) Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU1_VOICE7: { dfsiFrame[1U] = 0; // RS (24,12,13) dfsiFrame[2U] = 0; // RS (24,12,13) dfsiFrame[3U] = 0; // RS (24,12,13) Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU1_VOICE8: { dfsiFrame[1U] = 0; // RS (24,12,13) dfsiFrame[2U] = 0; // RS (24,12,13) dfsiFrame[3U] = 0; // RS (24,12,13) Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU1_VOICE9: { dfsiFrame[1U] = 0; // LSD MSB dfsiFrame[2U] = 0; // LSD LSB Buffer.BlockCopy(imbe, 0, dfsiFrame, 4, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU1_VOICE1: default: { dfsiFrame[6U] = 0; // RSSI Buffer.BlockCopy(imbe, 0, dfsiFrame, 10, IMBE_BUF_LEN); // IMBE } break; } Buffer.BlockCopy(dfsiFrame, 0, data, offset, (int)frameLength); } /// /// Creates an P25 LDU1 frame message. /// /// /// public void CreateP25LDU1Message(in byte[] netLDU1, ref byte[] data, uint srcId, uint dstId) { // pack DFSI data int count = P25_MSG_HDR_SIZE; byte[] imbe = new byte[IMBE_BUF_LEN]; Buffer.BlockCopy(netLDU1, 10, imbe, 0, IMBE_BUF_LEN); EncodeLDU1(ref data, 24, imbe, P25DFSI.P25_DFSI_LDU1_VOICE1, srcId, dstId); count += (int)P25DFSI.P25_DFSI_LDU1_VOICE1_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU1, 26, imbe, 0, IMBE_BUF_LEN); EncodeLDU1(ref data, 46, imbe, P25DFSI.P25_DFSI_LDU1_VOICE2, srcId, dstId); count += (int)P25DFSI.P25_DFSI_LDU1_VOICE2_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU1, 55, imbe, 0, IMBE_BUF_LEN); EncodeLDU1(ref data, 60, imbe, P25DFSI.P25_DFSI_LDU1_VOICE3, srcId, dstId); count += (int)P25DFSI.P25_DFSI_LDU1_VOICE3_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU1, 80, imbe, 0, IMBE_BUF_LEN); EncodeLDU1(ref data, 77, imbe, P25DFSI.P25_DFSI_LDU1_VOICE4, srcId, dstId); count += (int)P25DFSI.P25_DFSI_LDU1_VOICE4_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU1, 105, imbe, 0, IMBE_BUF_LEN); EncodeLDU1(ref data, 94, imbe, P25DFSI.P25_DFSI_LDU1_VOICE5, srcId, dstId); count += (int)P25DFSI.P25_DFSI_LDU1_VOICE5_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU1, 130, imbe, 0, IMBE_BUF_LEN); EncodeLDU1(ref data, 111, imbe, P25DFSI.P25_DFSI_LDU1_VOICE6, srcId, dstId); count += (int)P25DFSI.P25_DFSI_LDU1_VOICE6_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU1, 155, imbe, 0, IMBE_BUF_LEN); EncodeLDU1(ref data, 128, imbe, P25DFSI.P25_DFSI_LDU1_VOICE7, srcId, dstId); count += (int)P25DFSI.P25_DFSI_LDU1_VOICE7_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU1, 180, imbe, 0, IMBE_BUF_LEN); EncodeLDU1(ref data, 145, imbe, P25DFSI.P25_DFSI_LDU1_VOICE8, srcId, dstId); count += (int)P25DFSI.P25_DFSI_LDU1_VOICE8_FRAME_LENGTH_BYTES; Buffer.BlockCopy(netLDU1, 204, imbe, 0, IMBE_BUF_LEN); EncodeLDU1(ref data, 162, imbe, P25DFSI.P25_DFSI_LDU1_VOICE9, srcId, dstId); count += (int)P25DFSI.P25_DFSI_LDU1_VOICE9_FRAME_LENGTH_BYTES; data[23U] = (byte)count; } /// /// Encode a logical link data unit 2. /// /// /// /// /// private void EncodeLDU2(ref byte[] data, int offset, byte[] imbe, byte frameType, CryptoParams cryptoParams) { if (data == null) throw new ArgumentNullException("data"); if (imbe == null) throw new ArgumentNullException("imbe"); // determine the LDU2 DFSI frame length, its variable uint frameLength = P25DFSI.P25_DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES; switch (frameType) { case P25DFSI.P25_DFSI_LDU2_VOICE10: frameLength = P25DFSI.P25_DFSI_LDU2_VOICE10_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU2_VOICE11: frameLength = P25DFSI.P25_DFSI_LDU2_VOICE11_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU2_VOICE12: frameLength = P25DFSI.P25_DFSI_LDU2_VOICE12_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU2_VOICE13: frameLength = P25DFSI.P25_DFSI_LDU2_VOICE13_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU2_VOICE14: frameLength = P25DFSI.P25_DFSI_LDU2_VOICE14_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU2_VOICE15: frameLength = P25DFSI.P25_DFSI_LDU2_VOICE15_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU2_VOICE16: frameLength = P25DFSI.P25_DFSI_LDU2_VOICE16_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU2_VOICE17: frameLength = P25DFSI.P25_DFSI_LDU2_VOICE17_FRAME_LENGTH_BYTES; break; case P25DFSI.P25_DFSI_LDU2_VOICE18: frameLength = P25DFSI.P25_DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES; break; default: return; } byte[] dfsiFrame = new byte[frameLength]; dfsiFrame[0U] = frameType; // Frame Type // different frame types mean different things switch (frameType) { case P25DFSI.P25_DFSI_LDU2_VOICE11: { Buffer.BlockCopy(imbe, 0, dfsiFrame, 1, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU2_VOICE12: { 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] = 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] = 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] = cryptoParams.AlgId; // Algorithm ID FneUtils.WriteBytes(cryptoParams.KeyId, ref dfsiFrame, 2); // Key ID Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU2_VOICE16: { // first 3 bytes of frame are supposed to be // part of the RS(24, 16, 9) of the VOICE12, 13, 14, 15 // control bytes Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU2_VOICE17: { // first 3 bytes of frame are supposed to be // part of the RS(24, 16, 9) of the VOICE12, 13, 14, 15 // control bytes Buffer.BlockCopy(imbe, 0, dfsiFrame, 5, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU2_VOICE18: { dfsiFrame[1U] = 0; // LSD MSB dfsiFrame[2U] = 0; // LSD LSB Buffer.BlockCopy(imbe, 0, dfsiFrame, 4, IMBE_BUF_LEN); // IMBE } break; case P25DFSI.P25_DFSI_LDU2_VOICE10: default: { dfsiFrame[6U] = 0; // RSSI Buffer.BlockCopy(imbe, 0, dfsiFrame, 10, IMBE_BUF_LEN); // IMBE } break; } Buffer.BlockCopy(dfsiFrame, 0, data, offset, (int)frameLength); } /// /// Creates an P25 LDU2 frame message. /// /// Input LDU data array /// Output data array 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, 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, 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, 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, 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, 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, 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, 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, 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, cryptoParams); count += (int)P25DFSI.P25_DFSI_LDU2_VOICE18_FRAME_LENGTH_BYTES; data[23U] = (byte)count; } /// /// Event handler used to process incoming P25 data. /// /// /// protected override void P25DataReceived(object sender, P25DataReceivedEvent e) { DateTime pktTime = DateTime.Now; if (e.DUID == P25DUID.HDU || e.DUID == P25DUID.TSDU || e.DUID == P25DUID.PDU) return; if (e.CallType == CallType.GROUP) { if (e.SrcId == 0) return; mainWindow.P25DataReceived(e, pktTime); } } } // 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; } } }