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 6d584577a..9c9d2d229 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 @@ -671,6 +671,10 @@ private void processAMBTC(P25P1Message message) urr.getResponse() + " UNIT REGISTRATION"); } break; + case OSP_IDENTIFIER_UPDATE_TDMA: + //Ignore - in the extended format it is carrying a frequency band for a foreign system and we + //don't allow that to corrupt the real frequency bands for this system. + break; default: // mLog.debug("Unrecognized AMBTC Opcode: " + ambtc.getHeader().getOpcode().name()); break; 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 8b8e25090..df994f75e 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 @@ -42,6 +42,7 @@ 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.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; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCGroupAffiliationResponse; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCGroupDataChannelGrant; @@ -245,6 +246,8 @@ public static P25P1Message createAMBTC(PDUSequence pduSequence, int nac, long ti return new AMBTCGroupAffiliationResponse(pduSequence, nac, timestamp); case OSP_GROUP_VOICE_CHANNEL_GRANT: return new AMBTCGroupVoiceChannelGrant(pduSequence, nac, timestamp); + case OSP_IDENTIFIER_UPDATE_TDMA: + return new AMBTCFrequencyBandUpdateTDMA(pduSequence, nac, timestamp); case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: return new AMBTCIndividualDataChannelGrant(pduSequence, nac, timestamp); case OSP_MESSAGE_UPDATE: diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCFrequencyBandUpdateTDMA.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCFrequencyBandUpdateTDMA.java new file mode 100644 index 000000000..bd38617eb --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCFrequencyBandUpdateTDMA.java @@ -0,0 +1,196 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 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.bits.FragmentedIntField; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.bits.LongField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; +import io.github.dsheirer.module.decode.p25.reference.ChannelType; +import java.util.Collections; +import java.util.List; +import org.apache.commons.math3.util.FastMath; + +/** + * Frequency band update TDMA multi-block format for a foreign system. + * + * Note: removed the IFrequencyBand interface for this message so that it doesn't get processed as a frequency band for + * the current system which would likely collide with the real frequency band for the current system. + */ +public class AMBTCFrequencyBandUpdateTDMA extends AMBTCMessage // implements IFrequencyBand +{ + private static final IntField HEADER_IDENTIFIER = IntField.length4(OCTET_3_BIT_24); + private static final IntField HEADER_CHANNEL_TYPE = IntField.length4(OCTET_3_BIT_24 + 4); + private static final FragmentedIntField HEADER_WACN = FragmentedIntField.of(32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 64, 65, 66, 67); + private static final IntField HEADER_SYSTEM = IntField.length12(OCTET_8_BIT_64 + 4); + private static final LongField BLOCK_0_BASE_FREQUENCY = LongField.length32(OCTET_0_BIT_0); + private static final int BLOCK_0_TRANSMIT_OFFSET_SIGN = OCTET_4_BIT_32; + private static final IntField BLOCK_0_TRANSMIT_OFFSET = IntField.range(OCTET_4_BIT_32 + 1, OCTET_4_BIT_32 + 13); + private static final IntField BLOCK_0_CHANNEL_SPACING = IntField.range(OCTET_5_BIT_40 + 6, OCTET_5_BIT_40 + 15); + + private ChannelType mChannelType; + private Identifier mWacn; + private Identifier mSystem; + + /** + * Constructs an instance + * @param PDUSequence containing the header and block 0 + * @param nac for the system + * @param timestamp of the PDU sequence + */ + public AMBTCFrequencyBandUpdateTDMA(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" WACN:").append(getWacn()); + sb.append(" FOREIGN SYSTEM:").append(getSystem()); + sb.append(" ID:").append(getIdentifier()); + sb.append(" OFFSET:").append(getTransmitOffset()); + sb.append(" SPACING:").append(getChannelSpacing()); + sb.append(" BASE:").append(getBaseFrequency()); + sb.append(" ").append(getChannelType()); + return sb.toString(); + } + + public Identifier getWacn() + { + if(mWacn == null) + { + mWacn = APCO25Wacn.create(getHeader().getMessage().getInt(HEADER_WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getHeader().getMessage().getInt(HEADER_SYSTEM)); + } + + return mSystem; + } + + public ChannelType getChannelType() + { + if(mChannelType == null) + { + mChannelType = ChannelType.fromValue(getHeader().getMessage().getInt(HEADER_CHANNEL_TYPE)); + } + + return mChannelType; + } + + public int getIdentifier() + { + return getHeader().getMessage().getInt(HEADER_IDENTIFIER); + } + + public long getChannelSpacing() + { + if(hasDataBlock(0)) + { + return getDataBlock(0).getMessage().getInt(BLOCK_0_CHANNEL_SPACING) * 125L; + } + + return 0; + } + + public long getBaseFrequency() + { + if(hasDataBlock(0)) + { + return getDataBlock(0).getMessage().getLong(BLOCK_0_BASE_FREQUENCY) * 5L; + } + + return 0L; + } + + public int getBandwidth() + { + return getChannelType().getBandwidth(); + } + + public long getTransmitOffset() + { + if(hasDataBlock(0)) + { + long offset = getDataBlock(0).getMessage().getInt(BLOCK_0_TRANSMIT_OFFSET) * getChannelSpacing(); + + if(!getDataBlock(0).getMessage().get(BLOCK_0_TRANSMIT_OFFSET_SIGN)) + { + offset *= -1; + } + + return offset; + } + + return 0L; + } + + /** + * Indicates if the frequency band has a transmit option for the subscriber unit. + */ + public boolean hasTransmitOffset() + { + return hasDataBlock(0) && getDataBlock(0).getMessage().getInt(BLOCK_0_TRANSMIT_OFFSET) != 0x80; + } + + public long getDownlinkFrequency(int channelNumber) + { + return getBaseFrequency() + (getChannelSpacing() * (int)(FastMath.floor(channelNumber / getTimeslotCount()))); + } + + public long getUplinkFrequency(int channelNumber) + { + if(hasTransmitOffset()) + { + return getDownlinkFrequency(channelNumber) + getTransmitOffset(); + } + + return 0; + } + + public boolean isTDMA() + { + return getChannelType().isTDMA(); + } + + public int getTimeslotCount() + { + return getChannelType().getSlotsPerCarrier(); + } + + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +}