From c8a97e273acd58e2e46811002200291fcc82615d Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 2 Jan 2026 10:29:10 -0500 Subject: [PATCH] add support for DVM's internal LC_CALL_TERM TSBK; --- FneSystemBase.cs | 27 +++++++++- P25/P25Defines.cs | 1 + P25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cs | 80 +++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 P25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cs diff --git a/FneSystemBase.cs b/FneSystemBase.cs index 2699275..03f6f28 100644 --- a/FneSystemBase.cs +++ b/FneSystemBase.cs @@ -7,7 +7,7 @@ * @package DVM / Fixed Network Equipment Core Library * @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0) * -* Copyright (C) 2024-2025 Bryan Biedenkapp, N2PLL +* Copyright (C) 2024-2026 Bryan Biedenkapp, N2PLL * */ @@ -22,6 +22,7 @@ using fnecore.EDAC; using fnecore.DMR; using fnecore.P25; using fnecore.NXDN; +using fnecore.P25.LC.TSBK; namespace fnecore { @@ -526,6 +527,30 @@ namespace fnecore peer.SendMaster(FneBase.CreateOpcode(Constants.NET_FUNC_PROTOCOL, Constants.NET_PROTOCOL_SUBFUNC_P25), payload, Constants.RtpCallEndSeq, callData.TxStreamID); } + /// + /// Helper to send a DVM call termination TSDU. + /// + /// + /// + public void SendDVMCallTermination(uint srcId, uint dstId) + { + OSP_DVM_LC_CALL_TERM osp = new OSP_DVM_LC_CALL_TERM(dstId, srcId); + + RemoteCallData callData = new RemoteCallData + { + MFId = P25Defines.P25_MFG_DVM_OCS, + SrcId = srcId, + DstId = dstId, + LCO = P25Defines.LC_CALL_TERM + }; + + byte[] tsbk = new byte[P25Defines.P25_TSBK_LENGTH_BYTES]; + + osp.Encode(ref tsbk); + + SendP25TSBK(callData, tsbk); + } + /// /// Helper to send a P25 TDU message. /// diff --git a/P25/P25Defines.cs b/P25/P25Defines.cs index 345fb85..0328497 100644 --- a/P25/P25Defines.cs +++ b/P25/P25Defines.cs @@ -154,6 +154,7 @@ namespace fnecore.P25 public const byte P25_FT_DATA_UNIT = 0x00; public const byte P25_MFG_STANDARD = 0x00; + public const byte P25_MFG_DVM_OCS = 0x9C; public const byte P25_ALGO_UNENCRYPT = 0x80; public const byte P25_ALGO_DES = 0x81; diff --git a/P25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cs b/P25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cs new file mode 100644 index 0000000..543a4f1 --- /dev/null +++ b/P25/lc/tsbk/OSP_DVM_LC_CALL_TERM.cs @@ -0,0 +1,80 @@ +// 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) 2026 Bryan Biedenkapp, N2PLL +* +*/ + +namespace fnecore.P25.LC.TSBK +{ + /// + /// OSP_DVM_LC_CALL_TERM TSBK + /// + public class OSP_DVM_LC_CALL_TERM : TSBKBase + { + public byte GrpVchId; + public uint GrpVchNo; + public uint DstId; + public uint SrcId; + + /// + /// Creates an instance of + /// + /// + /// + public OSP_DVM_LC_CALL_TERM(uint dstId = 0, uint srcId = 0) + { + DstId = dstId; + SrcId = srcId; + Lco = P25Defines.LC_CALL_TERM; + } + + /// + /// Decode CALL_ALRT TSBK + /// + /// + /// + /// + public override bool Decode(byte[] data, bool rawTSBK = true) + { + if (!base.Decode(data, rawTSBK)) + return false; + + ulong tsbkValue = FneUtils.ToUInt64(Payload, 0); + + GrpVchId = (byte)((tsbkValue >> 52) & 0x0F); // Channel ID + GrpVchNo = (uint)((tsbkValue >> 40) & 0xFFF); // Channel Number + DstId = (uint)((tsbkValue >> 24) & 0xFFFF); // Target Radio Address + SrcId = (uint)(tsbkValue & 0xFFFFFF); // Source Radio Address + + return true; + } + + /// + /// Encode CALL_ALRT TSBK + /// + /// + /// + /// + /// + public override void Encode(ref byte[] data, bool rawTSBK = true, bool noTrellis = true) + { + ulong tsbkValue = 0; + tsbkValue = (tsbkValue << 4) + GrpVchNo; // Channel ID + tsbkValue = (tsbkValue << 12) + GrpVchId; // Channel Number + tsbkValue = (tsbkValue << 16) + DstId; // Target Radio Address + tsbkValue = (tsbkValue << 24) + SrcId; // Source Radio Address + + FneUtils.Memset(Payload, 0x00, Payload.Length); + FneUtils.WriteBytes(tsbkValue, ref Payload, 0); + + base.Encode(ref data, rawTSBK, noTrellis); + } + } // public class OSP_DVM_LC_CALL_TERM +} // namespace fnecore.P25.LC.TSBK