From 5d9d40836b32721bac08cd704b449e20d9214a9f Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 8 Nov 2026 04:51:47 -0500 Subject: [PATCH] #2566 P25P1 Adjacent Status Broadcast message update. --- .../decode/p25/phase1/P25P1DecoderState.java | 10 +- .../P25P1NetworkConfigurationMonitor.java | 16 +- .../phase1/message/pdu/PDUMessageFactory.java | 4 +- ...CAdjacentStatusBroadcastUncoordinated.java | 146 ++++++++++++++++++ 4 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCAdjacentStatusBroadcastUncoordinated.java diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java index b1b0fb46..a97b6943 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java @@ -101,7 +101,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCIn import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMessageUpdate; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMotorolaGroupRegroupChannelGrant; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast; -import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCProtectionParameterBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCStatusUpdate; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCTelephoneInterconnectChannelGrant; @@ -599,6 +598,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList //Network configuration messages case OSP_ADJACENT_STATUS_BROADCAST: + case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN: mNetworkConfigurationMonitor.process(ambtc); break; case OSP_NETWORK_STATUS_BROADCAST: @@ -678,14 +678,6 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList mu.getShortDataMessage()); } break; - case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN: - if(ambtc instanceof AMBTCProtectionParameterBroadcast ppb) - { - broadcastEvent(ambtc, DecodeEventType.RESPONSE, "USE ENCRYPTION " + ppb.getEncryptionKey() + - " OUTBOUND MI:" + ppb.getOutboundMessageIndicator() + - " INBOUND MI:" + ppb.getInboundMessageIndicator()); - } - break; case OSP_ROAMING_ADDRESS_UPDATE: broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.RESPONSE, "ROAMING ADDRESS UPDATE"); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1NetworkConfigurationMonitor.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1NetworkConfigurationMonitor.java index 0b290cf1..a2446e38 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1NetworkConfigurationMonitor.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1NetworkConfigurationMonitor.java @@ -35,6 +35,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSeconda import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSystemServiceBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcast; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcastUncoordinated; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage; @@ -198,6 +199,7 @@ public class P25P1NetworkConfigurationMonitor switch(ambtc.getHeader().getOpcode()) { case OSP_ADJACENT_STATUS_BROADCAST: + case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN: if(ambtc instanceof AMBTCAdjacentStatusBroadcast) { AMBTCAdjacentStatusBroadcast aasb = (AMBTCAdjacentStatusBroadcast)ambtc; @@ -502,14 +504,24 @@ public class P25P1NetworkConfigurationMonitor if(mAMBTCNeighborSites.containsKey(site)) { AMBTCAdjacentStatusBroadcast ambtc = mAMBTCNeighborSites.get(site); - sb.append(" SYSTEM:").append(format(ambtc.getSystem(), 3)); + sb.append(" "); + if(ambtc instanceof AMBTCAdjacentStatusBroadcastUncoordinated unc) + { + sb.append("WACN:").append(unc.getWacn()).append(" "); + } + sb.append("SYSTEM:").append(format(ambtc.getSystem(), 3)); sb.append(" NAC:").append(format(ambtc.getNAC(), 3)); sb.append(" RFSS:").append(format(ambtc.getRfss(), 2)); sb.append(" SITE:").append(format(ambtc.getSite(), 2)); sb.append(" LRA:").append(format(ambtc.getLocationRegistrationArea(), 2)); sb.append(" CHANNEL:").append(ambtc.getChannel()); sb.append(" DOWNLINK:").append(ambtc.getChannel().getDownlinkFrequency()); - sb.append(" UPLINK:").append(ambtc.getChannel().getUplinkFrequency()).append("\n"); + sb.append(" UPLINK:").append(ambtc.getChannel().getUplinkFrequency()); + if(ambtc instanceof AMBTCAdjacentStatusBroadcastUncoordinated unc) + { + sb.append(" SERVICES:").append(unc.getSystemServiceClass()); + } + sb.append("\n"); } if(mLCNeighborSites.containsKey(site)) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUMessageFactory.java index 8e803b8e..d2a36e26 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUMessageFactory.java @@ -37,6 +37,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUn import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitToUnitAnswerResponse; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitToUnitVoiceServiceRequest; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcast; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcastUncoordinated; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCCallAlert; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCFrequencyBandUpdateTDMA; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCGroupAffiliationQuery; @@ -47,7 +48,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCIn import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMessageUpdate; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMotorolaGroupRegroupChannelGrant; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast; -import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCProtectionParameterBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRoamingAddressResponse; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRoamingAddressUpdate; @@ -337,7 +337,7 @@ public class PDUMessageFactory case OSP_NETWORK_STATUS_BROADCAST: return new AMBTCNetworkStatusBroadcast(pduSequence, nac, timestamp); case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN: - return new AMBTCProtectionParameterBroadcast(pduSequence, nac, timestamp); + return new AMBTCAdjacentStatusBroadcastUncoordinated(pduSequence, nac, timestamp); case OSP_RFSS_STATUS_BROADCAST: return new AMBTCRFSSStatusBroadcast(pduSequence, nac, timestamp); case OSP_ROAMING_ADDRESS_UPDATE: diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCAdjacentStatusBroadcastUncoordinated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCAdjacentStatusBroadcastUncoordinated.java new file mode 100644 index 00000000..4e6fb822 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCAdjacentStatusBroadcastUncoordinated.java @@ -0,0 +1,146 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp; + +import io.github.dsheirer.channel.IBandChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; + +/** + * Adjacent Status Broadcast - Uncoordinated Band Plan (opcode 0x3E), extended/multi-block (AMBTC) format. + * + * Announces a neighbor site that may belong to a different WACN/system (and band plan) than the current site. + * Opcode 0x3E was previously defined as Protection Parameter Broadcast; current TIA-102.AABC revisions reassign it. + * + * Header: LRA (24-31), flags (32-35), SYSTEM (36-47), RFSS (64-71), SITE (72-79) -- same as opcode 0x3C + * Block 0: TRANSMIT CHANNEL (0-15), RECEIVE CHANNEL (16-31), SYSTEM SERVICE CLASS (32-39), WACN (40-59) + * + * A receive channel of 0xFFFF indicates that no separate receive channel is specified. + */ +public class AMBTCAdjacentStatusBroadcastUncoordinated extends AMBTCAdjacentStatusBroadcast +{ + private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {0, 1, 2, 3}; + private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_UPLINK_CHANNEL = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_UPLINK_FREQUENCY_BAND = {16, 17, 18, 19}; + private static final int[] BLOCK_0_UPLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_SYSTEM_SERVICE_CLASS = {32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_WACN = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59}; + private static final int UNSPECIFIED_CHANNEL = 0xFFFF; + + private IBandChannelDescriptor mChannel; + private Identifier mWacn; + private SystemServiceClass mSystemServiceClass; + + public AMBTCAdjacentStatusBroadcastUncoordinated(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" WACN:").append(getWacn()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" RFSS:").append(getRfss()); + sb.append(" SITE:").append(getSite()); + sb.append(" CHANNEL:").append(getChannel()); + sb.append(" SERVICES:").append(getSystemServiceClass()); + return sb.toString(); + } + + /** + * WACN of the neighbor site + */ + public Identifier getWacn() + { + if(mWacn == null && hasDataBlock(0)) + { + mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); + } + + return mWacn; + } + + /** + * Services offered by the neighbor site + */ + public SystemServiceClass getSystemServiceClass() + { + if(mSystemServiceClass == null) + { + mSystemServiceClass = new SystemServiceClass(hasDataBlock(0) ? + getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_SERVICE_CLASS) : 0); + } + + return mSystemServiceClass; + } + + private boolean hasUplinkChannel() + { + return hasDataBlock(0) && getDataBlock(0).getMessage().getInt(BLOCK_0_UPLINK_CHANNEL) != UNSPECIFIED_CHANNEL; + } + + @Override + public boolean isExtendedChannel() + { + return hasUplinkChannel() && super.isExtendedChannel(); + } + + @Override + public IBandChannelDescriptor getChannel() + { + if(mChannel == null) + { + if(hasDataBlock(0)) + { + UnconfirmedDataBlock block = getDataBlock(0); + + if(isExtendedChannel()) + { + mChannel = APCO25ExplicitChannel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER), + block.getMessage().getInt(BLOCK_0_UPLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND), + block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER)); + } + } + else + { + mChannel = APCO25Channel.create(-1, 0); + } + } + + return mChannel; + } +}