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