/** * Digital Voice Modem - Fixed Network Equipment * 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 * */ // // Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) // Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) // /* * Copyright (C) 2022 by Bryan Biedenkapp N2PLL * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. */ using System; using fnecore.EDAC; namespace fnecore.P25 { /// /// Represents the data block for PDU P25 packets. /// public class DataBlock { private Trellis trellis; private byte headerSap; private byte[] data; /// Sets the data block serial number. public byte SerialNo; /// Flag indicating this is the last block in a sequence of block. public bool LastBlock; /// Logical link ID. public uint LLId; /// Service access point. public byte SAP; /// /// Data format. /// public byte Format; /* ** Methods */ /// /// Initializes a new instance of the class. /// public DataBlock() { SerialNo = 0; LastBlock = false; LLId = 0; SAP = 0; trellis = new Trellis(); Format = P25Defines.PDU_FMT_CONFIRMED; headerSap = 0; data = new byte[P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; } /// /// Decodes P25 PDU data block. /// /// /// Instance of the DataHeader class. /// True, if data block was decoded, otherwise false. public bool Decode(byte[] pduData, DataHeader header) { if (pduData == null) throw new NullReferenceException("pduData"); byte[] buffer = new byte[P25Defines.P25_PDU_CONFIRMED_LENGTH_BYTES]; Format = header.Format; headerSap = header.SAP; // set these to reasonable defaults SerialNo = 0; LastBlock = false; LLId = 0; if (Format == P25Defines.PDU_FMT_CONFIRMED) { // decode 3/4 rate Trellis bool valid = trellis.Decode34(pduData, ref buffer); if (!valid) { return false; } SerialNo = (byte)((buffer[0] & 0xFEU) >> 1); // Confirmed Data Serial No. ushort crc = (ushort)(((buffer[0] & 0x01U) << 8) + buffer[1]); // CRC-9 Check Sum data = new byte[P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES]; // if this is extended addressing and the first block decode the SAP and LLId if (headerSap == P25Defines.PDU_SAP_EXT_ADDR && SerialNo == 0U) { SAP = (byte)(buffer[5U] & 0x3FU); // Service Access Point LLId = (uint)((buffer[2U] << 16) + (buffer[3U] << 8) + buffer[4U]); // Logical Link ID for (uint i = 0; i < P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; i++) data[i] = buffer[i + 2]; // Payload Data } else { for (uint i = 0; i < P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; i++) data[i] = buffer[i + 2]; // Payload Data } // compute CRC-9 for the packet ushort calculated = CRC.Crc9(buffer, 144); if (((crc ^ calculated) != 0) && ((crc ^ calculated) != 0x1FFU)) System.Diagnostics.Trace.WriteLine($"P25_DUID_PDU, fmt = {Format}, invalid crc = {crc} != {calculated}"); } else if ((Format == P25Defines.PDU_FMT_UNCONFIRMED) || (Format == P25Defines.PDU_FMT_RSP) || (Format == P25Defines.PDU_FMT_AMBT)) { // decode 1/2 rate Trellis bool valid = trellis.Decode12(pduData, ref buffer); if (!valid) return false; data = new byte[P25Defines.P25_PDU_UNCONFIRMED_LENGTH_BYTES]; for (uint i = 0; i < P25Defines.P25_PDU_UNCONFIRMED_LENGTH_BYTES; i++) data[i] = buffer[i]; // Payload Data } else return false; // unknown format value return true; } /// /// Encodes a P25 PDU data block. /// /// Buffer to encode data block to. public void Encode(ref byte[] pduData) { if (pduData == null) throw new NullReferenceException("pduData"); if (Format == P25Defines.PDU_FMT_CONFIRMED) { byte[] buffer = new byte[P25Defines.P25_PDU_CONFIRMED_LENGTH_BYTES]; buffer[0U] = (byte)((SerialNo << 1) & 0xFEU); // Confirmed Data Serial No. // if this is extended addressing and the first block decode the SAP and LLId if (headerSap == P25Defines.PDU_SAP_EXT_ADDR && SerialNo == 0U) { buffer[5U] = (byte)(SAP & 0x3FU); // Service Access Point buffer[2U] = (byte)((LLId >> 16) & 0xFFU); // Logical Link ID buffer[3U] = (byte)((LLId >> 8) & 0xFFU); buffer[4U] = (byte)((LLId >> 0) & 0xFFU); for (uint i = 0; i < P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES - 4U; i++) buffer[i + 6] = data[i]; } else { for (uint i = 0; i < P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; i++) buffer[i + 2] = data[i]; } ushort crc = CRC.Crc9(buffer, 144); buffer[0U] = (byte)(buffer[0U] + ((crc >> 8) & 0x01U)); // CRC-9 Check Sum (b8) buffer[1U] = (byte)(crc & 0xFFU); // CRC-9 Check Sum (b0 - b7) trellis.Encode34(buffer, ref pduData); } else if (Format == P25Defines.PDU_FMT_UNCONFIRMED || Format == P25Defines.PDU_FMT_RSP || Format == P25Defines.PDU_FMT_AMBT) { byte[] buffer = new byte[P25Defines.P25_PDU_UNCONFIRMED_LENGTH_BYTES]; for (uint i = 0; i < P25Defines.P25_PDU_UNCONFIRMED_LENGTH_BYTES - 4U; i++) buffer[i] = data[i]; trellis.Encode12(buffer, ref pduData); } else return; // unknown format value } /// /// Sets the raw data stored in the data block. /// /// public void SetData(byte[] buffer) { if (buffer == null) throw new NullReferenceException("buffer"); if (Format == P25Defines.PDU_FMT_CONFIRMED) { for (uint i = 0; i < P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; i++) data[i] = buffer[i]; } else if (Format == P25Defines.PDU_FMT_UNCONFIRMED || Format == P25Defines.PDU_FMT_RSP || Format == P25Defines.PDU_FMT_AMBT) { for (uint i = 0; i < P25Defines.P25_PDU_UNCONFIRMED_LENGTH_BYTES; i++) data[i] = buffer[i]; } else return; // unknown format value } /// /// Gets the raw data stored in the data block. /// /// public uint GetData(ref byte[] buffer) { if (buffer == null) throw new NullReferenceException("buffer"); if (Format == P25Defines.PDU_FMT_CONFIRMED) { for (uint i = 0; i < P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; i++) buffer[i] = data[i]; return P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; } else if (Format == P25Defines.PDU_FMT_UNCONFIRMED || Format == P25Defines.PDU_FMT_RSP || Format == P25Defines.PDU_FMT_AMBT) { for (uint i = 0; i < P25Defines.P25_PDU_UNCONFIRMED_LENGTH_BYTES; i++) data[i] = buffer[i]; return P25Defines.P25_PDU_UNCONFIRMED_LENGTH_BYTES; } else return 0; // unknown format value } } // public class DataBlock } // namespace fnecore.P25