diff --git a/EDAC/Trellis.cs b/EDAC/Trellis.cs index 679b772..12b8518 100644 --- a/EDAC/Trellis.cs +++ b/EDAC/Trellis.cs @@ -11,7 +11,7 @@ // Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) // /* -* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* 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 diff --git a/P25/DataBlock.cs b/P25/DataBlock.cs index df1ee87..d843fe9 100644 --- a/P25/DataBlock.cs +++ b/P25/DataBlock.cs @@ -11,7 +11,7 @@ // Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) // /* -* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* 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 @@ -224,8 +224,10 @@ namespace fnecore.P25 /// /// Gets the raw data stored in the data block. /// + /// + /// /// - public uint GetData(ref byte[] buffer) + public uint GetData(ref byte[] buffer, uint offset) { if (buffer == null) throw new NullReferenceException("buffer"); @@ -233,13 +235,13 @@ namespace fnecore.P25 if (Format == P25Defines.PDU_FMT_CONFIRMED) { for (uint i = 0; i < P25Defines.P25_PDU_CONFIRMED_DATA_LENGTH_BYTES; i++) - buffer[i] = data[i]; + buffer[i + offset] = 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]; + buffer[i + offset] = data[i]; return P25Defines.P25_PDU_UNCONFIRMED_LENGTH_BYTES; } else diff --git a/P25/DataHeader.cs b/P25/DataHeader.cs index d554f3b..3f1dc41 100644 --- a/P25/DataHeader.cs +++ b/P25/DataHeader.cs @@ -11,7 +11,7 @@ // Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) // /* -* Copyright (C) 2022 by Bryan Biedenkapp N2PLL +* 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 @@ -413,14 +413,16 @@ namespace fnecore.P25 /// /// Gets the raw header data. /// + /// + /// /// - public uint GetData(ref byte[] buffer) + public uint GetData(ref byte[] buffer, uint offset) { if (buffer == null) throw new NullReferenceException("buffer"); for (uint i = 0; i < P25Defines.P25_PDU_HEADER_LENGTH_BYTES; i++) - buffer[i] = data[i]; + buffer[i + offset] = data[i]; return P25Defines.P25_PDU_HEADER_LENGTH_BYTES; } diff --git a/P25/P25Defines.cs b/P25/P25Defines.cs index 366c169..4a1681b 100644 --- a/P25/P25Defines.cs +++ b/P25/P25Defines.cs @@ -137,11 +137,19 @@ namespace fnecore.P25 public const byte P25_MI_LENGTH = 9; + public const byte P25_LDU_FRAME_LENGTH_BYTES = 216; + + public const byte P25_MAX_PDU_COUNT = 32; + public const uint P25_MAX_PDU_LENGTH = 512; + public const byte P25_PDU_HEADER_LENGTH_BYTES = 12; public const byte P25_PDU_CONFIRMED_LENGTH_BYTES = 18; public const byte P25_PDU_CONFIRMED_DATA_LENGTH_BYTES = 16; public const byte P25_PDU_UNCONFIRMED_LENGTH_BYTES = 12; + public const byte P25_PDU_FEC_LENGTH_BYTES = 25; + public const byte P25_PDU_FEC_LENGTH_BITS = (byte)(P25_PDU_FEC_LENGTH_BYTES * 8U - 4U); // Trellis is actually 196 bits + // PDU Format Type(s) public const byte PDU_FMT_RSP = 0x03; public const byte PDU_FMT_UNCONFIRMED = 0x15; diff --git a/P25/PDUAssembler.cs b/P25/PDUAssembler.cs new file mode 100644 index 0000000..4903be8 --- /dev/null +++ b/P25/PDUAssembler.cs @@ -0,0 +1,282 @@ +/** +* 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