/** * 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) 2023 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 System.Collections.Generic; using fnecore.EDAC; namespace fnecore.P25 { /// /// /// public enum PDUDecodeState { IDLE = 0, DECODING = 1 } // public enum PDUDecodeState /// /// Implements helpers for dealing with FNE P25 PDU traffic. /// public class PDUAssembler { private PDUDecodeState decodeState; private uint dataOffset; private uint dataBlockCnt; private uint pduCount; private byte[] netPDU; private byte[] data; /// /// PDU Header. /// public DataHeader Header; /// /// PDU Second Header. /// public DataHeader SecondHeader; /// /// Flag indicating whether or not we are utilizing a secondary header. /// public bool UseSecondHeader; /// /// /// public bool ExtendedAddress; /// /// Data blocks. /// public List Blocks; /// /// Raw PDU user data. /// public byte[] UserData { get { return data; } } /// /// Length of raw PDU user data. /// public uint UserDataLength; /* ** Methods */ /// /// Initializes a new instance of the class. /// public PDUAssembler() { decodeState = PDUDecodeState.IDLE; dataOffset = 0; dataBlockCnt = 0; pduCount = 0; netPDU = new byte[P25Defines.P25_MAX_PDU_COUNT * P25Defines.P25_LDU_FRAME_LENGTH_BYTES + 2U]; data = new byte[P25Defines.P25_MAX_PDU_COUNT * P25Defines.P25_PDU_CONFIRMED_LENGTH_BYTES + 2U]; UserDataLength = 0; Header = new DataHeader(); SecondHeader = new DataHeader(); UseSecondHeader = false; ExtendedAddress = false; Blocks = new List(); } /// /// Helper used to decode PDU traffic from the FNE network. /// /// /// public bool Decode(byte[] data, uint blockLength) { if (decodeState == PDUDecodeState.IDLE) { Header.Reset(); SecondHeader.Reset(); dataOffset = 0; dataBlockCnt = 0; pduCount = 0; decodeState = PDUDecodeState.DECODING; byte[] buffer = new byte[P25Defines.P25_PDU_FEC_LENGTH_BYTES]; for (uint i = 0; i < P25Defines.P25_PDU_FEC_LENGTH_BYTES; i++) buffer[i] = data[i + 24]; bool ret = Header.Decode(buffer); if (!ret) { // unfixable PDU data Header.Reset(); SecondHeader.Reset(); dataBlockCnt = 0; pduCount = 0; decodeState = PDUDecodeState.IDLE; return false; } // make sure we don't get a PDU with more blocks then we support if (Header.BlocksToFollow >= P25Defines.P25_MAX_PDU_COUNT) { // too many PDU blocks to process Header.Reset(); SecondHeader.Reset(); dataOffset = 0; dataBlockCnt = 0; pduCount = 0; decodeState = PDUDecodeState.IDLE; return false; } pduCount++; } if (decodeState == PDUDecodeState.DECODING) { for (uint i = 0; i < blockLength; i++) netPDU[i + dataOffset] = data[i + 24]; dataOffset += blockLength; pduCount++; dataBlockCnt++; if (dataBlockCnt >= Header.BlocksToFollow) { byte blocksToFollow = Header.BlocksToFollow; uint offset = 0; byte[] buffer = new byte[P25Defines.P25_PDU_FEC_LENGTH_BYTES]; // process second header if we're using enhanced addressing if (Header.SAP == P25Defines.PDU_SAP_EXT_ADDR && Header.Format == P25Defines.PDU_FMT_UNCONFIRMED) { for (uint i = 0; i < P25Defines.P25_PDU_FEC_LENGTH_BYTES; i++) buffer[i] = netPDU[i]; bool ret = SecondHeader.Decode(buffer); if (!ret) { // unfixable PDU data Header.Reset(); SecondHeader.Reset(); dataBlockCnt = 0; pduCount = 0; UseSecondHeader = false; decodeState = PDUDecodeState.IDLE; return false; } UseSecondHeader = true; offset += P25Defines.P25_PDU_FEC_LENGTH_BYTES; blocksToFollow--; } dataBlockCnt = 0U; // process all blocks in the data stream uint dataOffset = 0U; // if we are using a secondary header place it in the PDU user data buffer if (UseSecondHeader) { SecondHeader.GetData(ref data, dataOffset); dataOffset += P25Defines.P25_PDU_HEADER_LENGTH_BYTES; UserDataLength += P25Defines.P25_PDU_HEADER_LENGTH_BYTES; } // decode data blocks for (uint i = 0U; i < blocksToFollow; i++) { for (uint j = 0; j < P25Defines.P25_PDU_FEC_LENGTH_BYTES; j++) buffer[j] = netPDU[j + offset]; DataBlock block = new DataBlock(); bool ret = block.Decode(buffer, (UseSecondHeader) ? SecondHeader : Header); if (ret) { // if we are getting unconfirmed or confirmed blocks, and if we've reached the total number of blocks // set this block as the last block for full packet CRC if ((Header.Format == P25Defines.PDU_FMT_CONFIRMED) || (Header.Format == P25Defines.PDU_FMT_UNCONFIRMED)) { if ((dataBlockCnt + 1U) == blocksToFollow) block.LastBlock = true; } // are we processing extended address data from the first block? if (Header.SAP == P25Defines.PDU_SAP_EXT_ADDR && Header.Format == P25Defines.PDU_FMT_CONFIRMED && block.SerialNo == 0U) { SecondHeader.Reset(); SecondHeader.AckNeeded = true; SecondHeader.Format = block.Format; SecondHeader.LLId = block.LLId; SecondHeader.SAP = block.SAP; ExtendedAddress = true; } block.GetData(ref data, dataOffset); dataOffset += (Header.Format == P25Defines.PDU_FMT_CONFIRMED) ? P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES : P25Defines.P25_PDU_UNCONFIRMED_LENGTH_BYTES; UserDataLength = dataOffset; dataBlockCnt++; Blocks.Add(block); // is this the last block? if (block.LastBlock && dataBlockCnt == blocksToFollow) { bool crcRet = CRC.CheckCRC32(data, UserDataLength); if (!crcRet) return false; else return true; } } offset += P25Defines.P25_PDU_FEC_LENGTH_BYTES; } decodeState = PDUDecodeState.IDLE; } } return false; } } // public class PDUAssembler } // namespace fnecore.P25