P25P1: decode unconfirmed data PDUs with a packet data SAP as IP packets (#2506)

PDU header format 0x15 is the generic unconfirmed data header; unconfirmed
multi-block trunking control (UMBTC) is only its trunking control SAP
(61/63) use.  PDUHeaderFactory built a plain PDUHeader for every 0x15
header and PDUMessageFactory sent every such sequence to createUMBTC(), so
outbound unconfirmed IP data (SNDCP RF UNCONFIRMED DATA with SAP 0, 4 or
6) was logged raw as

  PDU HEADER FORMAT:UNCONFIRMED MBTC UNCONFIRMED VENDOR:STANDARD TO LLID:n
  DATA BLOCKS:4 MSG:4100450000...

while the confirmed equivalent (format 0x16) was decoded as IPPKT with
its LRRP / ARS payload.

- PDUHeaderFactory: build a PacketHeader for a 0x15 header whose SAP is
  UNENCRYPTED_USER_DATA, PACKET_DATA or SNDCP_PACKET_DATA_CONTROL.  Other
  SAPs, including trunking control 61/63, keep the plain PDUHeader.
- PDUMessageFactory: route a 0x15 sequence whose header is a PacketHeader
  through createPacketData(); everything else stays on the UMBTC path.

No payload changes are needed: PacketMessage already sizes unconfirmed
data blocks at 12 octets, and the header fields it uses (SAP, pad octet
count, data header offset) sit at the same bit positions in the confirmed
and unconfirmed headers.  The ARS RegistrationAcknowledgement parser only
reads the extension octet, so the 5-octet trailer (0x04 + 32-bit time)
some systems append does not affect it.

Adds PDUMessageFactoryTest, built from two unconfirmed PDUs captured on a
Motorola P25 Phase 1 system (ARS registration acknowledgements to UDP
port 4005, one with the trailer), plus header routing checks for SAP
0/4/6, SAP 61/63 and the confirmed 0x16 header.

Co-authored-by: csmith_ai <carrottop@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Christopher Smith
2026-11-13 01:42:53 -06:00
committed by GitHub
parent fbc54a38dd
commit 1a90eaf6c6
3 changed files with 271 additions and 1 deletions

View File

@@ -24,6 +24,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCHeader
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketHeader;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.response.ResponseHeader;
import io.github.dsheirer.module.decode.p25.reference.PDUFormat;
import io.github.dsheirer.module.decode.p25.reference.ServiceAccessPoint;
public class PDUHeaderFactory
{
@@ -41,8 +42,41 @@ public class PDUHeaderFactory
case RESPONSE_PACKET_HEADER_FORMAT:
return new ResponseHeader(correctedBinaryMessage, passesCRC);
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
//Format 0x15 is the generic unconfirmed data header. Unconfirmed multi-block trunking control is
//only the trunking control SAP (61/63) use of it. With a packet data SAP the header has the same
//field layout as the confirmed packet data header and the data blocks carry an (IP) packet.
if(isPacketData(getServiceAccessPoint(correctedBinaryMessage)))
{
return new PacketHeader(correctedBinaryMessage, passesCRC);
}
return new PDUHeader(correctedBinaryMessage, passesCRC);
default:
return new PDUHeader(correctedBinaryMessage, passesCRC);
}
}
/**
* Service Access Point (SAP) from a packet data or unconfirmed data header message.
*/
public static ServiceAccessPoint getServiceAccessPoint(CorrectedBinaryMessage correctedBinaryMessage)
{
return ServiceAccessPoint.fromValue(correctedBinaryMessage.getInt(PacketHeader.SAP_ID));
}
/**
* Indicates if the SAP identifies packet data that is processed by the PDUMessageFactory as a packet message.
*/
public static boolean isPacketData(ServiceAccessPoint sap)
{
switch(sap)
{
case UNENCRYPTED_USER_DATA:
case PACKET_DATA:
case SNDCP_PACKET_DATA_CONTROL:
return true;
default:
return false;
}
}
}

View File

@@ -173,6 +173,13 @@ public class PDUMessageFactory
case RESPONSE_PACKET_HEADER_FORMAT:
return new ResponseMessage(pduSequence, nac, timestamp);
case UNCONFIRMED_MULTI_BLOCK_TRUNKING_CONTROL:
//The header factory creates a packet header when the unconfirmed data header carries a packet data
//SAP, so that unconfirmed IP packets are processed the same as confirmed (PACKET_DATA) packets.
if(pduSequence.getHeader() instanceof PacketHeader)
{
return createPacketData(pduSequence, nac, timestamp);
}
return createUMBTC(pduSequence, nac, timestamp);
default:
return new PDUSequenceMessage(pduSequence, nac, timestamp);
@@ -229,7 +236,7 @@ public class PDUMessageFactory
}
/**
* Creates a packet data message
* Creates a packet data message from a confirmed or unconfirmed packet data sequence.
*
* @param pduSequence containing a packet data header and zero or more data blocks
* @param nac network access code