diff --git a/.idea/dictionaries/denny.xml b/.idea/dictionaries/denny.xml index 99c24efd9..fc47979f4 100644 --- a/.idea/dictionaries/denny.xml +++ b/.idea/dictionaries/denny.xml @@ -29,6 +29,7 @@ talkgroups tgid ultravox + unsynchronized wacn xtea diff --git a/src/main/java/io/github/dsheirer/alias/AliasList.java b/src/main/java/io/github/dsheirer/alias/AliasList.java index f13ad04e6..be2721759 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasList.java +++ b/src/main/java/io/github/dsheirer/alias/AliasList.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -663,11 +663,13 @@ public TalkgroupAliasList() public Alias getAlias(TalkgroupIdentifier identifier) { + //Attempt to do a fully qualified identifier match first. if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti) { - return mFullyQualifiedTalkgroupAliasMap.get(fqti.toString()); + return mFullyQualifiedTalkgroupAliasMap.get(fqti.getFullyQualifiedTalkgroupAddress()); } + //Then try to match it by it's locally assigned (temporary) address. int value = identifier.getValue(); Alias mapValue = mTalkgroupAliasMap.get(value); @@ -676,6 +678,7 @@ public Alias getAlias(TalkgroupIdentifier identifier) return mapValue; } + //Finally, match the locally assigned address against any talkgroup ranges for(Map.Entry entry : mTalkgroupRangeAliasMap.entrySet()) { if(entry.getKey().contains(value)) @@ -780,11 +783,13 @@ public RadioAliasList() public Alias getAlias(RadioIdentifier identifier) { + //Attempt to do a fully qualified identifier match first. if(identifier instanceof FullyQualifiedRadioIdentifier fqri) { - return mFullyQualifiedRadioAliasMap.get(fqri.toString()); + return mFullyQualifiedRadioAliasMap.get(fqri.getFullyQualifiedRadioAddress()); } + //Then match against the locally assigned (temporary) address int value = identifier.getValue(); Alias mapValue = mRadioAliasMap.get(value); @@ -793,6 +798,7 @@ public Alias getAlias(RadioIdentifier identifier) return mapValue; } + //Finally, attempt to match the locally assigned (temporary) address against any radio ranges. for(Map.Entry entry : mRadioRangeAliasMap.entrySet()) { if(entry.getKey().contains(value)) diff --git a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java index c78141920..f2872447c 100644 --- a/src/main/java/io/github/dsheirer/bits/BinaryMessage.java +++ b/src/main/java/io/github/dsheirer/bits/BinaryMessage.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -451,6 +451,203 @@ public int getInt(int[] bits) return value; } + /** + * Returns the integer value of the message field described by the field argument. + * @param intField with start and end indices (inclusive). + * @return integer field value. + */ + public int getInt(IntField intField) + { + int value = 0; + + for(int index = intField.start(); index <= intField.end(); index++) + { + value = Integer.rotateLeft(value, 1); + + if(get(index)) + { + value++; + } + } + + return value; + } + + /** + * Returns the integer value of the message field described by the field argument. + * @param fragmentedField with an array of message indices + * @return integer field value. + */ + public int getInt(FragmentedIntField fragmentedField) + { + int value = 0; + + for(int index: fragmentedField.indices()) + { + value = Integer.rotateLeft(value, 1); + + if(get(index)) + { + value++; + } + } + + return value; + } + + /** + * Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices. + * @param intField to inspect + * @return true if any of the bits are set, indicating a non-zero value. + */ + public boolean hasInt(IntField intField) + { + return nextSetBit(intField.start()) <= intField.end(); + } + + /** + * Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices. + * @param intField to inspect + * @return true if any of the bits are set, indicating a non-zero value. + */ + public boolean hasInt(FragmentedIntField fragmentedField) + { + for(int index: fragmentedField.indices()) + { + if(get(index)) + { + return true; + } + } + + return false; + } + + /** + * Returns the integer value of the message field described by the field argument where the message start index is + * offset within this binary message. + * @param intField with start and end indices (inclusive). + * @param offset to the start of the first message index. + * @return integer field value. + */ + public int getInt(IntField intField, int offset) + { + int value = 0; + + for(int index = intField.start() + offset; index <= intField.end() + offset; index++) + { + value = Integer.rotateLeft(value, 1); + + if(get(index)) + { + value++; + } + } + + return value; + } + + /** + * Returns the integer value of the message field described by the field argument where the message start index is + * offset within this binary message. + * @param fragmentedField with an array of field indices + * @param offset to the start of the first message index. + * @return integer field value. + */ + public int getInt(FragmentedIntField fragmentedField, int offset) + { + int value = 0; + + for(int index: fragmentedField.indices()) + { + value = Integer.rotateLeft(value, 1); + + if(get(index + offset)) + { + value++; + } + } + + return value; + } + + /** + * Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices. + * @param intField to inspect + * @param offset to the first bit of the message. + * @return true if any of the bits are set, indicating a non-zero value. + */ + public boolean hasInt(IntField intField, int offset) + { + return nextSetBit(intField.start() + offset) <= (intField.end() + offset); + } + + /** + * Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices. + * @param intField to inspect + * @param offset to the first bit of the message. + * @return true if any of the bits are set, indicating a non-zero value. + */ + public boolean hasInt(FragmentedIntField fragmentedField, int offset) + { + for(int index: fragmentedField.indices()) + { + if(get(index + offset)) + { + return true; + } + } + + return false; + } + + /** + * Returns the long value of the message field described by the field argument. + * @param intField with start and end indices (inclusive). + * @return integer field value. + */ + public long getLong(LongField intField) + { + long value = 0; + + for(int index = intField.start(); index <= intField.end(); index++) + { + value = Long.rotateLeft(value, 1); + + if(get(index)) + { + value++; + } + } + + return value; + } + + /** + * Returns the long value of the message field described by the field argument where the message start index is + * offset within this binary message. + * @param intField with start and end indices (inclusive). + * @param offset to the start of the first message index. + * @return field value. + */ + public long getLong(LongField intField, int offset) + { + long value = 0; + + for(int index = intField.start() + offset; index <= intField.end() + offset; index++) + { + value = Long.rotateLeft(value, 1); + + if(get(index)) + { + value++; + } + } + + return value; + } + + /** * Returns the integer value represented by the bit array * @@ -742,6 +939,18 @@ public String getHex(int start, int end) return sb.toString(); } + /** + * Returns the integer field formatted as a hex value using zero prefixes to pad the hex character count to fully + * represent the size (width) of the field. + * @param field to parse as hex + * @return hex value. + */ + public String getHex(IntField field) + { + int width = Math.ceilDiv(field.width(), 4); + return String.format("%0" + width + "X", getInt(field)); + } + /** * Format the byte value that starts at the specified index as hexadecimal. If the length of the message is less * than the start index plus 7 bits, then the value represents those bits as high-order bits with zero padding in diff --git a/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java b/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java index 40dfa1603..3f11a9c60 100644 --- a/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java +++ b/src/main/java/io/github/dsheirer/bits/CorrectedBinaryMessage.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.bits; @@ -84,9 +81,9 @@ public void incrementCorrectedBitCount(int additionalCount) /** * Returns a new binary message containing the bits from (inclusive) to end (exclusive). * - * @param start bit - * @param end bit - * @return message + * @param start bit inclusive + * @param end bit exclusive + * @return message with sub message bits */ public CorrectedBinaryMessage getSubMessage(int start, int end) { diff --git a/src/main/java/io/github/dsheirer/bits/FragmentedIntField.java b/src/main/java/io/github/dsheirer/bits/FragmentedIntField.java new file mode 100644 index 000000000..e9ca7360a --- /dev/null +++ b/src/main/java/io/github/dsheirer/bits/FragmentedIntField.java @@ -0,0 +1,45 @@ +/* + * ***************************************************************************** + * 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.bits; + +/** + * Defines a fragmented or non-contiguous bit field within a binary message. + * @param indices for the bits in the field. + */ +public record FragmentedIntField(int... indices) +{ + public FragmentedIntField + { + if(indices.length > 32) + { + throw new IllegalArgumentException("Integer field indices size [" + indices.length + "] cannot exceed 32-bits for an integer"); + } + } + + /** + * Utility constructor method. + * @param indices (inclusive) + * @return constructed fragmented integer field. + */ + public static FragmentedIntField of(int... indices) + { + return new FragmentedIntField(indices); + } +} diff --git a/src/main/java/io/github/dsheirer/bits/IntField.java b/src/main/java/io/github/dsheirer/bits/IntField.java new file mode 100644 index 000000000..4dc5dc91d --- /dev/null +++ b/src/main/java/io/github/dsheirer/bits/IntField.java @@ -0,0 +1,131 @@ +/* + * ***************************************************************************** + * 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.bits; + +/** + * Defines a contiguous bit field within a binary message. + * @param start bit index, inclusive. + * @param end bit index, inclusive. + */ +public record IntField(int start, int end) +{ + public IntField + { + if(end - start > 32) + { + throw new IllegalArgumentException("Integer field length [" + (end - start) + "] cannot exceed 32-bits for an integer"); + } + + if(end < start) + { + throw new IllegalArgumentException("Integer field start index must be less than end index"); + } + } + + /** + * Width of the field in bit positions + * @return width + */ + public int width() + { + return end() - start() + 1; + } + + /** + * Utility constructor method. + * @param start index (inclusive) + * @param end index (inclusive) + * @return constructed bit field. + */ + public static IntField range(int start, int end) + { + return new IntField(start, end); + } + + /** + * Utility constructor method for a field with one four bits of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static IntField length4(int start) + { + return new IntField(start, (start + 3)); + } + + /** + * Utility constructor method for a field with one octet of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static IntField length8(int start) + { + return new IntField(start, (start + 7)); + } + + /** + * Utility constructor method for a field with 12 bits of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static IntField length12(int start) + { + return new IntField(start, (start + 11)); + } + + /** + * Utility constructor method for a field with two octets of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static IntField length16(int start) + { + return new IntField(start, (start + 15)); + } + + /** + * Utility constructor method for a field with 20 bits of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static IntField length20(int start) + { + return new IntField(start, (start + 19)); + } + + /** + * Utility constructor method for a field with two octets of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static IntField length24(int start) + { + return new IntField(start, (start + 23)); + } + + /** + * Utility constructor method for a field with three octets of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static IntField length32(int start) + { + return new IntField(start, (start + 31)); + } +} diff --git a/src/main/java/io/github/dsheirer/bits/LongField.java b/src/main/java/io/github/dsheirer/bits/LongField.java new file mode 100644 index 000000000..9bf091021 --- /dev/null +++ b/src/main/java/io/github/dsheirer/bits/LongField.java @@ -0,0 +1,122 @@ +/* + * ***************************************************************************** + * 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.bits; + +/** + * Defines a contiguous bit field within a binary message. + * @param start bit index, inclusive. + * @param end bit index, inclusive. + */ +public record LongField(int start, int end) +{ + public LongField + { + if(end - start > 64) + { + throw new IllegalArgumentException("Long field length [" + (end - start) + "] cannot exceed 64-bits for a long"); + } + + if(end < start) + { + throw new IllegalArgumentException("Long field start index must be less than end index"); + } + } + + /** + * Utility constructor method. + * @param start index (inclusive) + * @param end index (inclusive) + * @return constructed bit field. + */ + public static LongField range(int start, int end) + { + return new LongField(start, end); + } + + /** + * Utility constructor method for a field with one four bits of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static LongField length4(int start) + { + return new LongField(start, (start + 3)); + } + + /** + * Utility constructor method for a field with one octet of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static LongField length8(int start) + { + return new LongField(start, (start + 7)); + } + + /** + * Utility constructor method for a field with 12 bits of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static LongField length12(int start) + { + return new LongField(start, (start + 11)); + } + + /** + * Utility constructor method for a field with two octets of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static LongField length16(int start) + { + return new LongField(start, (start + 15)); + } + + /** + * Utility constructor method for a field with 20 bits of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static LongField length20(int start) + { + return new LongField(start, (start + 19)); + } + + /** + * Utility constructor method for a field with two octets of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static LongField length24(int start) + { + return new LongField(start, (start + 23)); + } + + /** + * Utility constructor method for a field with three octets of length. + * @param start index (inclusive) + * @return constructed bit field. + */ + public static LongField length32(int start) + { + return new LongField(start, (start + 31)); + } +} diff --git a/src/main/java/io/github/dsheirer/channel/state/AbstractDecoderState.java b/src/main/java/io/github/dsheirer/channel/state/AbstractDecoderState.java index 198421013..8cd9a603f 100644 --- a/src/main/java/io/github/dsheirer/channel/state/AbstractDecoderState.java +++ b/src/main/java/io/github/dsheirer/channel/state/AbstractDecoderState.java @@ -26,6 +26,7 @@ import io.github.dsheirer.module.Module; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.ActivitySummaryProvider; +import io.github.dsheirer.module.decode.event.DecodeEventDuplicateDetector; import io.github.dsheirer.module.decode.event.IDecodeEvent; import io.github.dsheirer.module.decode.event.IDecodeEventProvider; import io.github.dsheirer.sample.Broadcaster; @@ -44,6 +45,7 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum protected Broadcaster mDecodeEventBroadcaster = new Broadcaster<>(); protected Listener mDecoderStateListener; private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener(); + private static final DecodeEventDuplicateDetector mDuplicateEventDetector = new DecodeEventDuplicateDetector(); private boolean mRunning; public abstract DecoderType getDecoderType(); @@ -105,7 +107,10 @@ public Listener getMessageListener() */ protected void broadcast(IDecodeEvent event) { - mDecodeEventBroadcaster.broadcast(event); + if(!mDuplicateEventDetector.isDuplicate(event, System.currentTimeMillis())) + { + mDecodeEventBroadcaster.broadcast(event); + } } /** @@ -161,6 +166,9 @@ public void removeDecoderStateListener() mDecoderStateListener = null; } + /** + * Wrapper that implements the IDecoderStateEventListener interface + */ private class DecoderStateEventListener implements Listener { @Override @@ -169,23 +177,4 @@ public void receive(DecoderStateEvent event) receiveDecoderStateEvent(event); } } - - /** - * Message listener that only passes messages while we're running. This is important because each of the decoders - * can process blocks of samples and that can result in additional messages being generated even after shutdown - * and so we shut off the processing of decoded messages when we're commanded to stop. The sample processing thread - * cannot be shutdown or forcefully interrupted because downstream decoder and channel states may have acquired - * locks that have to be properly released. - */ - private class MessageListener implements Listener - { - @Override - public void receive(IMessage message) - { - if(isRunning()) - { - receive(message); - } - } - } } diff --git a/src/main/java/io/github/dsheirer/channel/state/DecoderState.java b/src/main/java/io/github/dsheirer/channel/state/DecoderState.java index f6fe14a6b..40fc56ee0 100644 --- a/src/main/java/io/github/dsheirer/channel/state/DecoderState.java +++ b/src/main/java/io/github/dsheirer/channel/state/DecoderState.java @@ -25,6 +25,7 @@ import io.github.dsheirer.identifier.MutableIdentifierCollection; import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier; import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; import io.github.dsheirer.sample.Listener; /** @@ -38,13 +39,21 @@ public abstract class DecoderState extends AbstractDecoderState private MutableIdentifierCollection mIdentifierCollection; protected Listener mConfigurationIdentifierListener; protected IChannelDescriptor mCurrentChannel; + private long mCurrentFrequency; + /** + * Constructs an instance + * @param mutableIdentifierCollection to preload into this decoder state + */ public DecoderState(MutableIdentifierCollection mutableIdentifierCollection) { mIdentifierCollection = mutableIdentifierCollection; mIdentifierCollection.update(new DecoderTypeConfigurationIdentifier(getDecoderType())); } + /** + * Constructs an instance using an empty identifier collection. + */ public DecoderState() { this(new MutableIdentifierCollection()); @@ -59,6 +68,23 @@ public void start() mIdentifierCollection.broadcastIdentifiers(); } + /** + * Current frequency for this channel. + */ + public long getCurrentFrequency() + { + return mCurrentFrequency; + } + + /** + * Sets the current frequency for this channel. + * @param frequency to set. + */ + public void setCurrentFrequency(long frequency) + { + mCurrentFrequency = frequency; + } + /** * Registers the listener to receive identifier update notifications */ @@ -153,9 +179,13 @@ public void receive(IdentifierUpdateNotification identifierUpdateNotification) { getIdentifierCollection().receive(identifierUpdateNotification); - if(identifierUpdateNotification.getIdentifier() instanceof ChannelDescriptorConfigurationIdentifier) + if(identifierUpdateNotification.getIdentifier() instanceof ChannelDescriptorConfigurationIdentifier cdci) + { + setCurrentChannel(cdci.getValue()); + } + else if(identifierUpdateNotification.getIdentifier() instanceof FrequencyConfigurationIdentifier fci) { - setCurrentChannel(((ChannelDescriptorConfigurationIdentifier)identifierUpdateNotification.getIdentifier()).getValue()); + setCurrentFrequency(fci.getValue()); } } } diff --git a/src/main/java/io/github/dsheirer/channel/state/TimeslotDecoderState.java b/src/main/java/io/github/dsheirer/channel/state/TimeslotDecoderState.java index 85d5bd0c7..5d69c5248 100644 --- a/src/main/java/io/github/dsheirer/channel/state/TimeslotDecoderState.java +++ b/src/main/java/io/github/dsheirer/channel/state/TimeslotDecoderState.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -26,12 +26,20 @@ import io.github.dsheirer.identifier.IdentifierUpdateNotification; import io.github.dsheirer.identifier.MutableIdentifierCollection; import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; import io.github.dsheirer.sample.Listener; +/** + * Base decoder state implementation for a multi-timeslot protocol that tracks the state for a single timeslot. + */ public abstract class TimeslotDecoderState extends DecoderState { private int mTimeslot; + /** + * Constructs an instance + * @param timeslot to monitor/maintain state. + */ public TimeslotDecoderState(int timeslot) { super(new MutableIdentifierCollection(timeslot)); @@ -39,6 +47,9 @@ public TimeslotDecoderState(int timeslot) mConfigurationIdentifierListener = new TimeslotConfigurationIdentifierListener(); } + /** + * Monitored timeslot for this decoder state instance. + */ protected int getTimeslot() { return mTimeslot; @@ -82,6 +93,10 @@ else if(identifier instanceof IChannelDescriptor) { setCurrentChannel((IChannelDescriptor)identifier); } + else if(identifier instanceof FrequencyConfigurationIdentifier fci) + { + setCurrentFrequency(fci.getValue()); + } } } } diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java index 91b89e8c9..da13b8728 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java @@ -279,7 +279,7 @@ else if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFre break; case REQUEST_DISABLE: case NOTIFICATION_DELETE: - if(channel.isProcessing()) + if(channel != null && channel.isProcessing()) { try { diff --git a/src/main/java/io/github/dsheirer/edac/BerlekempMassey.java b/src/main/java/io/github/dsheirer/edac/BerlekempMassey.java index 55d0e0f9c..85ae78816 100644 --- a/src/main/java/io/github/dsheirer/edac/BerlekempMassey.java +++ b/src/main/java/io/github/dsheirer/edac/BerlekempMassey.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -19,6 +19,7 @@ package io.github.dsheirer.edac; +import io.github.dsheirer.log.LoggingSuppressor; import org.apache.commons.lang3.Validate; import org.apache.commons.math3.util.FastMath; import org.slf4j.Logger; @@ -32,6 +33,7 @@ public class BerlekempMassey { private final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class); + private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(mLog); /* Golay field size GF( 2 ** MM ) */ private int MM; @@ -234,7 +236,16 @@ public boolean decode(final int[] input, int[] output) //input, output /* put recd[i] into index form (ie as powers of alpha) */ for(int i = 0; i < NN; i++) { - output[i] = index_of[input[i]]; + try + { + output[i] = index_of[input[i]]; + } + catch(Exception e) + { + LOGGING_SUPPRESSOR.error(getClass().toString(), 2, "Reed Solomon Decoder error for " + + "class [" + getClass() + "] there may be an issue with the message parser class indices - " + + "ensure the hex bit values are not larger 63", e); + } } /* first form the syndromes */ diff --git a/src/main/java/io/github/dsheirer/edac/Binary40_9_16.java b/src/main/java/io/github/dsheirer/edac/Binary40_9_16.java new file mode 100644 index 000000000..68c9fc25b --- /dev/null +++ b/src/main/java/io/github/dsheirer/edac/Binary40_9_16.java @@ -0,0 +1,285 @@ +/* + * ***************************************************************************** + * 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.edac; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.IntField; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * P25 Phase 2 Binary (40, 9, 16) Code for IISCH in the Super Frame Fragment. + */ +public class Binary40_9_16 +{ + private final static Logger mLog = LoggerFactory.getLogger(Binary40_9_16.class); + private static final long[] CHECKSUMS = {0x8816CE36D7l, 0x201DFD4F64l, 0x100F4B1758l, 0x0C00DED18El, 0x020807F7FFl, + 0x09048D9B72l, 0x009DA3A171l, 0x0058CBAA4El, 0x00343D8597l}; + private static final long CODE_WORD_OFFSET = 0x184229d461l; + private static final Map CODEWORD_MAP_TS1 = new TreeMap<>(); + private static final Map CODEWORD_MAP_TS2 = new TreeMap<>(); + private static final IntField CHANNEL_NUMBER = IntField.range(2, 3); + private static final IntField ISCH_LOCATION = IntField.range(4, 5); + private static final IntField WORD = IntField.range(0, 8); + + public Binary40_9_16() + { + for(int x = 0; x < 512; x++) + { + BinaryMessage message = new BinaryMessage(9); + message.load(0, 9, x); + + int channelNumber = message.getInt(CHANNEL_NUMBER) + 1; + int ischLocation = message.getInt(ISCH_LOCATION); + + long codeword = getCodeWord(message); + codeword ^= CODE_WORD_OFFSET; + + if(ischLocation != 3) + { + if(channelNumber == 1) + { + CODEWORD_MAP_TS1.put(codeword, message); + } + else if(channelNumber == 2) + { + CODEWORD_MAP_TS2.put(codeword, message); + } + } + } + + List keys = new ArrayList<>(CODEWORD_MAP_TS1.keySet()); + Collections.sort(keys); + for(Long key: keys) + { + BinaryMessage message = CODEWORD_MAP_TS1.get(key); + int distance = getDistance(key, keys); + } + List keys2 = new ArrayList<>(CODEWORD_MAP_TS2.keySet()); + Collections.sort(keys2); + for(Long key: keys2) + { + BinaryMessage message = CODEWORD_MAP_TS2.get(key); + int distance = getDistance(key, keys); + } + } + + /** + * Calculates the closest distance between the value and the values in the value set. + * @param value to test + * @param valueSet to test against + * @return closest distance of the value to the values in the value set, ignoring any equivalent values. + */ + public static int getDistance(long value, List valueSet) + { + int distance = 40; + + for(Long candidate: valueSet) + { + if(candidate != value) + { + long syndrome = candidate ^ value; + int candidateDistance = Long.bitCount(syndrome); + if(candidateDistance < distance) + { + distance = candidateDistance; + } + } + } + + return distance; + } + + /** + * Creates a codeword from the binary message where each set bit in the binary message corresponds to one of the + * words in the generator and we XOR the words together. + * @param message for generating a codeword + * @return codeword + */ + public static long getCodeWord(BinaryMessage message) + { + long codeword = 0; + + for(int x = 0; x < message.length(); x++) + { + if(message.get(x)) + { + codeword ^= CHECKSUMS[x]; + } + } + + return codeword; + } + + /** + * Calculates the checksum (ie codeword) from the set bits in the message. + * @param message with set bits, 0-8 + * @return + */ + private static long calculateChecksum(BinaryMessage message) + { + long calculated = 0; //Starting value + + /* Iterate the set bits and XOR running checksum with lookup value */ + for(int i = message.nextSetBit(0); i < 9; i = message.nextSetBit(i + 1)) + { + calculated ^= CHECKSUMS[i]; + } + + return calculated; + } + +// /** +// * Performs error detection and returns a corrected copy of the 24-bit +// * message that starts at the start index. +// * +// * @param message - source message containing startIndex + 24 bits length +// * @return - corrected 24-bit galois value +// */ +// public static int checkAndCorrect(CorrectedBinaryMessage message) +// { +// boolean parityError = message.cardinality() % 2 != 0; +// +// long syndrome = getSyndrome(message); +// +// /* No errors */ +// if(syndrome == 0) +// { +// if(parityError) +// { +// message.flip(23); +// message.incrementCorrectedBitCount(1); +// return 1; +// } +// +// return 0; +// } +// +// /* Get original message value */ +// int original = message.getInt(0, 22); +// +// int index = -1; +// int syndromeWeight = 3; +// int errors = 0; +// +// while(index < 23) +// { +// if(index != -1) +// { +// /* restore the previous flipped bit */ +// if(index > 0) +// { +// message.flip(index - 1); +// } +// +// message.flip(index); +// +// syndromeWeight = 2; +// } +// +// syndrome = getSyndrome(message); +// +// if(syndrome > 0) +// { +// for(int i = 0; i < 23; i++) +// { +// +// errors = Long.bitCount(syndrome); +// +// if(errors <= syndromeWeight) +// { +// message.xor(12, 11, syndrome); +// +// message.rotateRight(i, 0, 22); +// +// if(index >= 0) +// { +// errors++; +// } +// +// int corrected = message.getInt(0, 22); +// +// if(Integer.bitCount(original ^ corrected) > 3) +// { +// return 2; +// } +// +// return 1; +// } +// else +// { +// message.rotateLeft(0, 22); +// syndrome = getSyndrome(message); +// } +// } +// +// index++; +// } +// } +// +// return 2; +// } + + private static long getSyndrome(BinaryMessage message) + { + long calculated = calculateChecksum(message); + long checksum = message.getInt(12, 22); + return (checksum ^ calculated); + } + + public static void main(String[] args) + { +// Binary40_9_16 edac = new Binary40_9_16(); + + long mask = 0xFFFFFFFFFFl; + long sync = 0x575D57F7FFl; + long syncLeft = Long.rotateLeft(sync, 2) & mask; + long syncRight = Long.rotateRight(sync, 2) & mask; + System.out.println("Mask: " + Long.toBinaryString(sync)); + System.out.println("Left: " + Long.toBinaryString(syncLeft)); + System.out.println("Right: " + Long.toBinaryString(syncRight)); + + int distanceLeft = Long.bitCount(sync ^ syncLeft); + int distanceRight = Long.bitCount( sync ^ syncRight); + + System.out.println("Left: " + distanceLeft); + System.out.println("Right: " + distanceRight); + + System.out.println("Finished!"); + + +// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20")); +// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0")); +// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00")); +// +// System.out.println("M:" + bm.toHexString()); +// int a = Binary40_9_16.checkAndCorrect(bm); +// System.out.println("M:" + bm.toHexString()); +// +// System.out.println("A:" + a); + + + } +} diff --git a/src/main/java/io/github/dsheirer/edac/CRCP25.java b/src/main/java/io/github/dsheirer/edac/CRCP25.java index a5b9c3895..35017c9d7 100644 --- a/src/main/java/io/github/dsheirer/edac/CRCP25.java +++ b/src/main/java/io/github/dsheirer/edac/CRCP25.java @@ -1,22 +1,26 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * 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 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. + * 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 - * - ******************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ package io.github.dsheirer.edac; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -216,6 +220,63 @@ public class CRCP25 0x20000000l, 0x40000000l, 0x80000000l }; + /** + * CRC-12 checksums for P25 Phase 2 MAC PDU Contents. This is used for a 144-bit message and 12-bits of checksum + * using a polynomial of 0x1897 and generated by: + *

+ * long[] checksums = CRCUtil.generate(144, 12, 0x1897l, 0x0l, false); + */ + private static int[] CRC_12_FACCH = new int[]{0x256, 0x12B, 0xCDE, 0x66F, 0xF7C, 0x7BE, 0x3DF, 0xDA4, 0x6D2, 0x369, + 0xDFF, 0xAB4, 0x55A, 0x2AD, 0xD1D, 0xAC5, 0x929, 0x8DF, 0x824, 0x412, 0x209, 0xD4F, 0xAEC, 0x576, 0x2BB, + 0xD16, 0x68B, 0xF0E, 0x787, 0xF88, 0x7C4, 0x3E2, 0x1F1, 0xCB3, 0xA12, 0x509, 0xECF, 0xB2C, 0x596, 0x2CB, + 0xD2E, 0x697, 0xF00, 0x780, 0x3C0, 0x1E0, 0xF0, 0x78, 0x3C, 0x1E, 0xF, 0xC4C, 0x626, 0x313, 0xDC2, 0x6E1, + 0xF3B, 0xBD6, 0x5EB, 0xEBE, 0x75F, 0xFE4, 0x7F2, 0x3F9, 0xDB7, 0xA90, 0x548, 0x2A4, 0x152, 0xA9, 0xC1F, + 0xA44, 0x522, 0x291, 0xD03, 0xACA, 0x565, 0xEF9, 0xB37, 0x9D0, 0x4E8, 0x274, 0x13A, 0x9D, 0xC05, 0xA49, + 0x96F, 0x8FC, 0x47E, 0x23F, 0xD54, 0x6AA, 0x355, 0xDE1, 0xABB, 0x916, 0x48B, 0xE0E, 0x707, 0xFC8, 0x7E4, + 0x3F2, 0x1F9, 0xCB7, 0xA10, 0x508, 0x284, 0x142, 0xA1, 0xC1B, 0xA46, 0x523, 0xEDA, 0x76D, 0xFFD, 0xBB5, + 0x991, 0x883, 0x80A, 0x405, 0xE49, 0xB6F, 0x9FC, 0x4FE, 0x27F, 0xD74, 0x6BA, 0x35D, 0xDE5, 0xAB9, 0x917, + 0x8C0, 0x460, 0x230, 0x118, 0x8C, 0x46, 0x23, 0xC5A, 0x62D, 0xF5D, 0xBE5, 0x9B9, 0x897}; + + /** + * CRC-12 checksums for P25 Phase 2 MAC PDU Contents. This is used for a 168-bit message and 12-bits of checksum + * using a polynomial of 0x1897 and generated by: + *

+ * long[] checksums = CRCUtil.generate(168, 12, 0x1897l, 0x0l, false); + */ + private static int[] CRC_12_SACCH = new int[]{0x47B, 0xE76, 0x73B, 0xFD6, 0x7EB, 0xFBE, 0x7DF, 0xFA4, 0x7D2, 0x3E9, + 0xDBF, 0xA94, 0x54A, 0x2A5, 0xD19, 0xAC7, 0x928, 0x494, 0x24A, 0x125, 0xCD9, 0xA27, 0x958, 0x4AC, 0x256, + 0x12B, 0xCDE, 0x66F, 0xF7C, 0x7BE, 0x3DF, 0xDA4, 0x6D2, 0x369, 0xDFF, 0xAB4, 0x55A, 0x2AD, 0xD1D, 0xAC5, + 0x929, 0x8DF, 0x824, 0x412, 0x209, 0xD4F, 0xAEC, 0x576, 0x2BB, 0xD16, 0x68B, 0xF0E, 0x787, 0xF88, 0x7C4, + 0x3E2, 0x1F1, 0xCB3, 0xA12, 0x509, 0xECF, 0xB2C, 0x596, 0x2CB, 0xD2E, 0x697, 0xF00, 0x780, 0x3C0, 0x1E0, + 0xF0, 0x78, 0x3C, 0x1E, 0xF, 0xC4C, 0x626, 0x313, 0xDC2, 0x6E1, 0xF3B, 0xBD6, 0x5EB, 0xEBE, 0x75F, 0xFE4, + 0x7F2, 0x3F9, 0xDB7, 0xA90, 0x548, 0x2A4, 0x152, 0xA9, 0xC1F, 0xA44, 0x522, 0x291, 0xD03, 0xACA, 0x565, + 0xEF9, 0xB37, 0x9D0, 0x4E8, 0x274, 0x13A, 0x9D, 0xC05, 0xA49, 0x96F, 0x8FC, 0x47E, 0x23F, 0xD54, 0x6AA, + 0x355, 0xDE1, 0xABB, 0x916, 0x48B, 0xE0E, 0x707, 0xFC8, 0x7E4, 0x3F2, 0x1F9, 0xCB7, 0xA10, 0x508, 0x284, + 0x142, 0xA1, 0xC1B, 0xA46, 0x523, 0xEDA, 0x76D, 0xFFD, 0xBB5, 0x991, 0x883, 0x80A, 0x405, 0xE49, 0xB6F, + 0x9FC, 0x4FE, 0x27F, 0xD74, 0x6BA, 0x35D, 0xDE5, 0xAB9, 0x917, 0x8C0, 0x460, 0x230, 0x118, 0x8C, 0x46, 0x23, + 0xC5A, 0x62D, 0xF5D, 0xBE5, 0x9B9, 0x897,}; + + /** + * CRC-16 checksums for P25 Phase 2 LCCH MAC PDU Contents. This is used for a 164-bit message and 16-bits of + * checksum using a polynomial of 0x11021 and generated by: + *

+ * long[] checksums = CRCUtil.generate(164, 16, 0x11021l, 0x0l, true); + */ + private static int[] CRC_16_LCCH = new int[]{0xCF76, 0x67BB, 0xBBCD, 0xD5F6, 0x6AFB, 0xBD6D, 0xD6A6, 0x6B53, 0xBDB9, + 0xD6CC, 0x6B66, 0x35B3, 0x92C9, 0xC174, 0x60BA, 0x305D, 0x903E, 0x481F, 0xAC1F, 0xDE1F, 0xE71F, 0xFB9F, + 0xF5DF, 0xF2FF, 0xF16F, 0xF0A7, 0xF043, 0xF031, 0xF008, 0x7804, 0x3C02, 0x1E01, 0x8710, 0x4388, 0x21C4, + 0x10E2, 0x871, 0x8C28, 0x4614, 0x230A, 0x1185, 0x80D2, 0x4069, 0xA824, 0x5412, 0x2A09, 0x9D14, 0x4E8A, + 0x2745, 0x9BB2, 0x4DD9, 0xAEFC, 0x577E, 0x2BBF, 0x9DCF, 0xC6F7, 0xEB6B, 0xFDA5, 0xF6C2, 0x7B61, 0xB5A0, + 0x5AD0, 0x2D68, 0x16B4, 0xB5A, 0x5AD, 0x8AC6, 0x4563, 0xAAA1, 0xDD40, 0x6EA0, 0x3750, 0x1BA8, 0xDD4, 0x6EA, + 0x375, 0x89AA, 0x44D5, 0xAA7A, 0x553D, 0xA28E, 0x5147, 0xA0B3, 0xD849, 0xE434, 0x721A, 0x390D, 0x9496, + 0x4A4B, 0xAD35, 0xDE8A, 0x6F45, 0xBFB2, 0x5FD9, 0xA7FC, 0x53FE, 0x29FF, 0x9CEF, 0xC667, 0xEB23, 0xFD81, + 0xF6D0, 0x7B68, 0x3DB4, 0x1EDA, 0xF6D, 0x8FA6, 0x47D3, 0xABF9, 0xDDEC, 0x6EF6, 0x377B, 0x93AD, 0xC1C6, + 0x60E3, 0xB861, 0xD420, 0x6A10, 0x3508, 0x1A84, 0xD42, 0x6A1, 0x8B40, 0x45A0, 0x22D0, 0x1168, 0x8B4, 0x45A, + 0x22D, 0x8906, 0x4483, 0xAA51, 0xDD38, 0x6E9C, 0x374E, 0x1BA7, 0x85C3, 0xCAF1, 0xED68, 0x76B4, 0x3B5A, + 0x1DAD, 0x86C6, 0x4363, 0xA9A1, 0xDCC0, 0x6E60, 0x3730, 0x1B98, 0xDCC, 0x6E6, 0x373, 0x89A9, 0xCCC4, 0x6662, + 0x3331, 0x9188, 0x48C4, 0x2462, 0x1231, 0x8108, 0x4084, 0x2042, 0x1021, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, + 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000}; + /** * Performs error detection and single-bit error correction against the * data blocks of a PDU1 message. @@ -402,11 +463,8 @@ else if(i > (messageStart + 15)) } int checksum = message.getInt(messageStart + 7, messageStart + 15); - int residual = calculated ^ checksum; -// mLog.debug( "CALC:" + calculated + " CHECK:" + checksum + " RESID:" + residual ); - if(residual == 0 || residual == 0x1FF) { return CRC.PASSED; @@ -441,12 +499,80 @@ public static boolean correctGalois24(CorrectedBinaryMessage tdulc) return passes; } + /** + * Calculates the CRC-12 checksum for the P25 Phase 2 MAC PDU Contents for S-OEMI/FACCH and compares it to the + * transmitted checksum to verify that the message is correct. + * Note: this uses the pre-calculated checksums in the CRC_12_FACCH array. + * + * @param message to verify + * @return true if the message passes the CRC-12 check. + */ + public static boolean crc12_FACCH(CorrectedBinaryMessage message) + { + int calculated = 0xFFF; //Initial fill of all ones. + + //Iterate the set bits and XOR the running checksum with pre-calculated lookup value + for(int i = message.nextSetBit(0); i >= 0 && i < 144; i = message.nextSetBit(i + 1)) + { + calculated ^= CRC_12_FACCH[i]; + } + + int checksum = message.getInt(144, 155); //12-bit transmitted checksum + int residual = calculated ^ checksum; + return residual == 0; + } + + /** + * Calculates the CRC-12 checksum for the P25 Phase 2 MAC PDU Contents for I-OEMI/SACCH and compares it to the + * transmitted checksum to verify that the message is correct. + * Note: this uses the pre-calculated checksums in the CRC_12_SACCH array. + * + * @param message to verify + * @return true if the message passes the CRC-12 check. + */ + public static boolean crc12_SACCH(CorrectedBinaryMessage message) + { + int calculated = 0xFFF; //Initial fill of all ones. + + //Iterate the set bits and XOR the running checksum with pre-calculated lookup value + for(int i = message.nextSetBit(0); i >= 0 && i < 168; i = message.nextSetBit(i + 1)) + { + calculated ^= CRC_12_SACCH[i]; + } + + int checksum = message.getInt(168, 179); //12-bit transmitted checksum + int residual = calculated ^ checksum; + return residual == 0; + } + + /** + * Calculates the CRC-16 checksum for the P25 Phase 2 MAC PDU Contents for I-OECI/LCCH and compares it to the + * transmitted checksum to verify that the message is correct. + * Note: this uses the pre-calculated checksums in the CRC_16_LCCH array. + * + * @param message to verify + * @return true if the message passes the CRC-16 check. + */ + public static boolean crc16_LCCH(CorrectedBinaryMessage message) + { + int calculated = 0xFFFF; //Initial fill of all ones. + + //Iterate the set bits and XOR the running checksum with pre-calculated lookup value + for(int i = message.nextSetBit(0); i >= 0 && i < 164; i = message.nextSetBit(i + 1)) + { + calculated ^= CRC_16_LCCH[i]; + } + + int checksum = message.getInt(164, 179); //16-bit transmitted checksum + int residual = calculated ^ checksum; + return residual == 0; + } + /** * Calculates the value of the message checksum as a long */ - public static long getLongChecksum(BinaryMessage message, - int crcStart, int crcLength) + public static long getLongChecksum(BinaryMessage message, int crcStart, int crcLength) { return message.getLong(crcStart, crcStart + crcLength - 1); } @@ -454,8 +580,7 @@ public static long getLongChecksum(BinaryMessage message, /** * Calculates the value of the message checksum as an integer */ - public static int getIntChecksum(BinaryMessage message, - int crcStart, int crcLength) + public static int getIntChecksum(BinaryMessage message, int crcStart, int crcLength) { return message.getInt(crcStart, crcStart + crcLength - 1); } @@ -494,16 +619,19 @@ public static int getBitError(int checksumError, int[] checksums) public static void main(String[] args) { - String raw = "000000001000001100000001010001111011000100001010010001111100000000000101000000000000000001000000000000110000000000000001101010101010101010101010"; - - BinaryMessage message = BinaryMessage.load(raw); - - mLog.debug("MSG:" + message.toString()); - - CRC results = checkCRC9(message, 0); - - mLog.debug("COR:" + message.toString()); - - mLog.debug("Results: " + results.getDisplayText()); + long[] checksums = CRCUtil.generate(144, 12, 0x1897l, 0x0l, false); + System.out.println("Checksums:" + Arrays.toString(checksums)); + + String hex12 = "7C0000000000000000000000000000000000CA7"; + +// BinaryMessage message = BinaryMessage.load(raw); +// +// mLog.debug("MSG:" + message.toString()); +// +// CRC results = checkCRC9(message, 0); +// +// mLog.debug("COR:" + message.toString()); +// +// mLog.debug("Results: " + results.getDisplayText()); } } diff --git a/src/main/java/io/github/dsheirer/edac/CRCUtil.java b/src/main/java/io/github/dsheirer/edac/CRCUtil.java index be7382df4..572c12181 100644 --- a/src/main/java/io/github/dsheirer/edac/CRCUtil.java +++ b/src/main/java/io/github/dsheirer/edac/CRCUtil.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -19,6 +19,7 @@ package io.github.dsheirer.edac; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,8 +33,7 @@ public static long[] generate(int messageSize, long initialFill, boolean includeCRCBitErrors) { - long[] crcTable = new long[messageSize + - (includeCRCBitErrors ? crcSize : 0)]; + long[] crcTable = new long[messageSize + (includeCRCBitErrors ? crcSize : 0)]; int[] checksumIndexes = new int[crcSize]; @@ -132,20 +132,12 @@ public static Parity parity(long value) * @return message with all message bits zeroed out, and the remainder * placed in the crc field which starts at index messageLength */ - public static BinaryMessage decode(BinaryMessage message, - int messageStart, - int messageSize, - long polynomial, - int crcSize) + public static BinaryMessage decode(BinaryMessage message, int messageStart, int messageSize, long polynomial, int crcSize) { - for(int i = message.nextSetBit(messageStart); - i >= messageStart && i < messageSize; - i = message.nextSetBit(i + 1)) + for(int i = message.nextSetBit(messageStart); i >= messageStart && i < messageSize; i = message.nextSetBit(i + 1)) { BinaryMessage polySet = new BinaryMessage(crcSize + i + 1); - polySet.load(i, crcSize + 1, polynomial); - message.xor(polySet); System.out.println(message.toString()); } @@ -226,8 +218,11 @@ public static void main(String[] args) { mLog.debug("Starting"); - long poly = 0x13l; - long[] checksums = generate(32, 4, poly, 0, true); + int messageSize = 164; + int crcSize = 16; + long poly = 0x11021l; + long initialFill = 0x0l; + long[] checksums = CRCUtil.generate(messageSize, crcSize, poly, initialFill, true); StringBuilder sb = new StringBuilder(); sb.append("private static int[] CHECKSUMS = new int[]{"); @@ -238,7 +233,29 @@ public static void main(String[] args) } sb.append("};"); - System.out.println("Checksums:\n" + sb); + + String hex = "7CFC0039420170452A4F9970000000000000000000D05"; + CorrectedBinaryMessage message = new CorrectedBinaryMessage(BinaryMessage.loadHex(hex)); + + + int calculated = 0xFFF; //Initial fill of all ones + + int messageStart = 0; + /* Iterate the set bits and XOR running checksum with lookup value */ + for(int i = message.nextSetBit(messageStart); + i >= messageStart && i < messageSize; + i = message.nextSetBit(i + 1)) + { + System.out.println("Bit [" + i + "] is set"); + calculated ^= checksums[i]; + } + + int checksum = message.getInt(messageSize, messageSize + crcSize - 1); + int residual = calculated ^ checksum; + + mLog.debug("CALC:" + Integer.toHexString(calculated).toUpperCase() + + " CHECK:" + Integer.toHexString(checksum).toUpperCase() + + " RESIDUAL:" + Integer.toHexString(residual).toUpperCase()); } } diff --git a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java index b42cf76e7..b09b21ae6 100644 --- a/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java +++ b/src/main/java/io/github/dsheirer/gui/JavaFxWindowManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -35,7 +35,7 @@ import io.github.dsheirer.gui.preference.UserPreferencesEditor; import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest; import io.github.dsheirer.gui.preference.calibration.CalibrationDialog; -import io.github.dsheirer.gui.viewer.RecordingViewer; +import io.github.dsheirer.gui.viewer.MessageRecordingViewer; import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest; import io.github.dsheirer.icon.IconModel; import io.github.dsheirer.jmbe.JmbeEditor; @@ -84,7 +84,7 @@ public class JavaFxWindowManager extends Application private TunerManager mTunerManager; private UserPreferences mUserPreferences; private UserPreferencesEditor mUserPreferencesEditor; - private RecordingViewer mRecordingViewer; + private MessageRecordingViewer mMessageRecordingViewer; private Stage mChannelMapStage; private Stage mIconManagerStage; @@ -216,14 +216,14 @@ public Stage getRecordingViewerStage() return mRecordingViewerStage; } - public RecordingViewer getRecordingViewer() + public MessageRecordingViewer getRecordingViewer() { - if(mRecordingViewer == null) + if(mMessageRecordingViewer == null) { - mRecordingViewer = new RecordingViewer(); + mMessageRecordingViewer = new MessageRecordingViewer(); } - return mRecordingViewer; + return mMessageRecordingViewer; } public Stage getIconManagerStage() diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java index d52615f42..28b3f5163 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/ChannelEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -647,11 +647,13 @@ public class NewP25P2ChannelMenu extends Menu public NewP25P2ChannelMenu() { setText(DecoderType.P25_PHASE2.getDisplayString()); - MenuItem trunked = new MenuItem("Trunked System"); - trunked.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE1)); - MenuItem channel = new MenuItem("Individual Channel"); + MenuItem trunkedP1 = new MenuItem("Trunked System - FDMA Phase 1 Control Channel"); + trunkedP1.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE1)); + MenuItem trunkedP2 = new MenuItem("Trunked System - TDMA Phase 2 Control Channel"); + trunkedP2.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE2)); + MenuItem channel = new MenuItem("Individual Phase 2 Channel"); channel.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE2)); - getItems().addAll(trunked, channel); + getItems().addAll(trunkedP1, trunkedP2, channel); } } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java index adb045677..632185621 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P1ConfigurationEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -113,7 +113,7 @@ private TitledPane getDecoderPane() if(mDecoderPane == null) { mDecoderPane = new TitledPane(); - mDecoderPane.setText("Decoder: P25 Phase 1 (or P25 Phase 2 Control Channel)"); + mDecoderPane.setText("Decoder: P25 Phase 1 (also used for P25 Phase 2 system with FDMA control channels)"); mDecoderPane.setExpanded(true); GridPane gridPane = new GridPane(); @@ -355,9 +355,9 @@ protected void saveDecoderConfiguration() { DecodeConfigP25Phase1 config; - if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase1) + if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase1 p1) { - config = (DecodeConfigP25Phase1)getItem().getDecodeConfiguration(); + config = p1; } else { diff --git a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java index a9a2759ad..1201b461a 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/channel/P25P2ConfigurationEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -42,9 +42,13 @@ import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.scene.control.Label; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; import javafx.scene.control.TitledPane; +import javafx.scene.control.Tooltip; import javafx.scene.layout.GridPane; import javafx.scene.layout.VBox; +import org.controlsfx.control.ToggleSwitch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,6 +68,8 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor private IntegerTextField mWacnTextField; private IntegerTextField mSystemTextField; private IntegerTextField mNacTextField; + private ToggleSwitch mIgnoreDataCallsButton; + private Spinner mTrafficChannelPoolSizeSpinner; /** * Constructs an instance @@ -102,7 +108,7 @@ private TitledPane getDecoderPane() if(mDecoderPane == null) { mDecoderPane = new TitledPane(); - mDecoderPane.setText("Decoder: P25 Phase 2"); + mDecoderPane.setText("Decoder: P25 Phase 2 for TDMA control or TDMA traffic channels."); mDecoderPane.setExpanded(true); GridPane gridPane = new GridPane(); @@ -112,6 +118,22 @@ private TitledPane getDecoderPane() int row = 0; + Label poolSizeLabel = new Label("Max Traffic Channels"); + GridPane.setHalignment(poolSizeLabel, HPos.RIGHT); + GridPane.setConstraints(poolSizeLabel, 0, row); + gridPane.getChildren().add(poolSizeLabel); + + GridPane.setConstraints(getTrafficChannelPoolSizeSpinner(), 1, row); + gridPane.getChildren().add(getTrafficChannelPoolSizeSpinner()); + + GridPane.setConstraints(getIgnoreDataCallsButton(), 2, row); + gridPane.getChildren().add(getIgnoreDataCallsButton()); + + Label directionLabel = new Label("Ignore Data Calls"); + GridPane.setHalignment(directionLabel, HPos.LEFT); + GridPane.setConstraints(directionLabel, 3, row); + gridPane.getChildren().add(directionLabel); + Label wacnLabel = new Label("WACN"); GridPane.setHalignment(wacnLabel, HPos.RIGHT); GridPane.setConstraints(wacnLabel, 0, ++row); @@ -122,20 +144,27 @@ private TitledPane getDecoderPane() Label systemLabel = new Label("System"); GridPane.setHalignment(systemLabel, HPos.RIGHT); - GridPane.setConstraints(systemLabel, 0, ++row); + GridPane.setConstraints(systemLabel, 2, row); gridPane.getChildren().add(systemLabel); - GridPane.setConstraints(getSystemTextField(), 1, row); + GridPane.setConstraints(getSystemTextField(), 3, row); gridPane.getChildren().add(getSystemTextField()); Label nacLabel = new Label("NAC"); GridPane.setHalignment(nacLabel, HPos.RIGHT); - GridPane.setConstraints(nacLabel, 0, ++row); + GridPane.setConstraints(nacLabel, 4, row); gridPane.getChildren().add(nacLabel); - GridPane.setConstraints(getNacTextField(), 1, row); + GridPane.setConstraints(getNacTextField(), 5, row); gridPane.getChildren().add(getNacTextField()); + Label noteLabel = new Label("Note: WACN/System/NAC values are auto-detected (ie not required) from " + + "the control channel and are only required when decoding individual traffic channels"); + GridPane.setHalignment(noteLabel, HPos.LEFT); + GridPane.setConstraints(noteLabel, 1, ++row, 6, 1); + gridPane.getChildren().add(noteLabel); + + mDecoderPane.setContent(gridPane); } @@ -203,6 +232,37 @@ private EventLogConfigurationEditor getEventLogConfigurationEditor() return mEventLogConfigurationEditor; } + private ToggleSwitch getIgnoreDataCallsButton() + { + if(mIgnoreDataCallsButton == null) + { + mIgnoreDataCallsButton = new ToggleSwitch(); + mIgnoreDataCallsButton.setDisable(true); + mIgnoreDataCallsButton.selectedProperty() + .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); + } + + return mIgnoreDataCallsButton; + } + + private Spinner getTrafficChannelPoolSizeSpinner() + { + if(mTrafficChannelPoolSizeSpinner == null) + { + mTrafficChannelPoolSizeSpinner = new Spinner(); + mTrafficChannelPoolSizeSpinner.setDisable(true); + mTrafficChannelPoolSizeSpinner.setTooltip( + new Tooltip("Maximum number of traffic channels that can be created by the decoder")); + mTrafficChannelPoolSizeSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); + SpinnerValueFactory svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 50); + mTrafficChannelPoolSizeSpinner.setValueFactory(svf); + mTrafficChannelPoolSizeSpinner.getValueFactory().valueProperty() + .addListener((observable, oldValue, newValue) -> modifiedProperty().set(true)); + } + + return mTrafficChannelPoolSizeSpinner; + } + private IntegerTextField getWacnTextField() { if(mWacnTextField == null) @@ -247,6 +307,9 @@ private RecordConfigurationEditor getRecordConfigurationEditor() types.add(RecorderType.BASEBAND); types.add(RecorderType.DEMODULATED_BIT_STREAM); types.add(RecorderType.MBE_CALL_SEQUENCE); + types.add(RecorderType.TRAFFIC_BASEBAND); + types.add(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM); + types.add(RecorderType.TRAFFIC_MBE_CALL_SEQUENCE); mRecordConfigurationEditor = new RecordConfigurationEditor(types); mRecordConfigurationEditor.setDisable(true); mRecordConfigurationEditor.modifiedProperty() @@ -259,9 +322,8 @@ private RecordConfigurationEditor getRecordConfigurationEditor() @Override protected void setDecoderConfiguration(DecodeConfiguration config) { - if(config instanceof DecodeConfigP25Phase2) + if(config instanceof DecodeConfigP25Phase2 decodeConfig) { - DecodeConfigP25Phase2 decodeConfig = (DecodeConfigP25Phase2)config; getWacnTextField().setDisable(false); getSystemTextField().setDisable(false); getNacTextField().setDisable(false); @@ -280,6 +342,11 @@ protected void setDecoderConfiguration(DecodeConfiguration config) getSystemTextField().set(0); getNacTextField().set(0); } + + getIgnoreDataCallsButton().setDisable(false); + getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls()); + getTrafficChannelPoolSizeSpinner().setDisable(false); + getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize()); } else { @@ -289,6 +356,8 @@ protected void setDecoderConfiguration(DecodeConfiguration config) getWacnTextField().setDisable(true); getSystemTextField().setDisable(true); getNacTextField().setDisable(true); + getIgnoreDataCallsButton().setDisable(true); + getTrafficChannelPoolSizeSpinner().setDisable(true); } } @@ -297,20 +366,23 @@ protected void saveDecoderConfiguration() { DecodeConfigP25Phase2 config; - if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase2) + if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase2 p2) { - config = (DecodeConfigP25Phase2)getItem().getDecodeConfiguration(); - config.setAutoDetectScrambleParameters(false); - int wacn = getWacnTextField().get(); - int system = getSystemTextField().get(); - int nac = getNacTextField().get(); - config.setScrambleParameters(new ScrambleParameters(wacn, system, nac)); + config = p2; } else { config = new DecodeConfigP25Phase2(); } + config.setAutoDetectScrambleParameters(false); + int wacn = getWacnTextField().get(); + int system = getSystemTextField().get(); + int nac = getNacTextField().get(); + config.setScrambleParameters(new ScrambleParameters(wacn, system, nac)); + config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected()); + config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue()); + getItem().setDecodeConfiguration(config); } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/EnrichedSite.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/EnrichedSite.java index 659c65c88..163339b01 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/EnrichedSite.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/EnrichedSite.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -25,13 +25,15 @@ /** * Wrapper class to join a Site and a corresponding County Info */ -public class EnrichedSite +public class EnrichedSite implements Comparable { + private static final String PHASE_2_TDMA_MODULATION = "TDMA"; private Site mSite; private CountyInfo mCountyInfo; /** * Constructs an instance + * * @param site object * @param countyInfo optional */ @@ -41,8 +43,16 @@ public EnrichedSite(Site site, CountyInfo countyInfo) mCountyInfo = countyInfo; } + public static String format(int value) + { + StringBuilder sb = new StringBuilder(); + sb.append(value).append(" (").append(Integer.toHexString(value).toUpperCase()).append(")"); + return sb.toString(); + } + /** * Site instance + * * @return site or null */ public Site getSite() @@ -52,6 +62,7 @@ public Site getSite() /** * Sets the site instance + * * @param site or null */ public void setSite(Site site) @@ -61,6 +72,7 @@ public void setSite(Site site) /** * County information + * * @return county info or null */ public CountyInfo getCountyInfo() @@ -70,6 +82,7 @@ public CountyInfo getCountyInfo() /** * Sets the county info + * * @param countyInfo or null */ public void setCountyInfo(CountyInfo countyInfo) @@ -78,13 +91,26 @@ public void setCountyInfo(CountyInfo countyInfo) } /** - * Optional site number + * Formatted system identity + * + * @return */ - public Integer getSiteNumber() + public String getSystemFormatted() { if(mSite != null) { - return mSite.getSiteNumber(); + //System number is stored in the zone number field. + return format(mSite.getZoneNumber()); + } + + return null; + } + + public String getSiteFormatted() + { + if(mSite != null) + { + return format(mSite.getSiteNumber()); } return null; @@ -93,11 +119,11 @@ public Integer getSiteNumber() /** * Optional site RFSS value */ - public Integer getRfss() + public String getRfssFormatted() { if(mSite != null) { - return mSite.getRfss(); + return format(mSite.getRfss()); } return null; @@ -128,4 +154,23 @@ public String getDescription() return null; } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + String system = getSystemFormatted(); + sb.append(system == null ? "-" : system).append(" "); + String rfss = getRfssFormatted(); + sb.append(rfss == null ? "-" : rfss).append(" "); + String site = getSiteFormatted(); + sb.append(site == null ? "-" : site); + return sb.toString(); + } + + @Override + public int compareTo(EnrichedSite o) + { + return this.toString().compareTo(o.toString()); + } } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceDecoder.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceDecoder.java index d0eb7b95f..8ef9655be 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceDecoder.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/RadioReferenceDecoder.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -449,6 +449,16 @@ else if(flavor.getName().contentEquals("Passport")) * @return decoder type or null. */ public DecoderType getDecoderType(System system) + { + return getDecoderType(system, null); + } + + /** + * Decoder type for the specified system, if supported. + * @param system requiring a decoder type + * @return decoder type or null. + */ + public DecoderType getDecoderType(System system, Site site) { Type type = getType(system); Flavor flavor = getFlavor(system); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SiteEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SiteEditor.java index 498d4bc9d..41890b68b 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SiteEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SiteEditor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -35,6 +35,7 @@ import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; import io.github.dsheirer.playlist.PlaylistManager; import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.rrapi.type.Flavor; import io.github.dsheirer.rrapi.type.RadioNetwork; import io.github.dsheirer.rrapi.type.Site; import io.github.dsheirer.rrapi.type.SiteFrequency; @@ -42,6 +43,13 @@ import io.github.dsheirer.rrapi.type.SystemInformation; import io.github.dsheirer.source.config.SourceConfigTuner; import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; import javafx.animation.RotateTransition; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleStringProperty; @@ -74,14 +82,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - public class SiteEditor extends GridPane { private static final Logger mLog = LoggerFactory.getLogger(SiteEditor.class); @@ -90,6 +90,8 @@ public class SiteEditor extends GridPane private static final String PRIMARY_CONTROL_CHANNEL = "d"; private static final String TOGGLE_BUTTON_CONTROL = "Control"; private static final String TOGGLE_BUTTON_P25_VOICE = "All P25 Voice"; + private static final String PHASE_2_TDMA_MODULATION = "TDMA"; + private static final String PHASE_2_FLAVOR = "Phase II"; private UserPreferences mUserPreferences; private PlaylistManager mPlaylistManager; @@ -115,6 +117,10 @@ public class SiteEditor extends GridPane private SystemInformation mCurrentSystemInformation; private ComboBox mAliasListNameComboBox; private Button mNewAliasListButton; + private Label mP25ControlLabel; + private SegmentedButton mP25ControlSegmentedButton; + private ToggleButton mFdmaControlToggleButton; + private ToggleButton mTdmaControlToggleButton; public SiteEditor(UserPreferences userPreferences, PlaylistManager playlistManager) { @@ -163,6 +169,13 @@ public SiteEditor(UserPreferences userPreferences, PlaylistManager playlistManag GridPane.setConstraints(getConfigurationsSegmentedButton(), 2, row); getChildren().add(getConfigurationsSegmentedButton()); + GridPane.setConstraints(getP25ControlLabel(), 1, ++row); + GridPane.setHalignment(getP25ControlLabel(), HPos.RIGHT); + getChildren().add(getP25ControlLabel()); + + GridPane.setConstraints(getP25ControlSegmentedButton(), 2, row); + getChildren().add(getP25ControlSegmentedButton()); + Label systemLabel = new Label("System"); GridPane.setConstraints(systemLabel, 1, ++row); GridPane.setHalignment(systemLabel, HPos.RIGHT); @@ -206,7 +219,7 @@ public SiteEditor(UserPreferences userPreferences, PlaylistManager playlistManag createBox.setAlignment(Pos.CENTER_LEFT); createBox.setSpacing(10); createBox.getChildren().addAll(getCreateChannelConfigurationButton(), getGoToChannelEditorCheckBox()); - GridPane.setConstraints(createBox, 2, ++row + 1); + GridPane.setConstraints(createBox, 2, ++row); getChildren().addAll(createBox); //Note: the following label node is added to the same location as the buttons and visibility is toggled @@ -215,6 +228,13 @@ public SiteEditor(UserPreferences userPreferences, PlaylistManager playlistManag getChildren().add(getProtocolNotSupportedLabel()); } + /** + * Creates a channel decode configuration for the specified site. + * @param decoderType to create + * @param site information and frequencies + * @param systemInformation with frequency mapping + * @return decode configuration + */ private DecodeConfiguration getDecodeConfiguration(DecoderType decoderType, Site site, SystemInformation systemInformation) { if(decoderType == null) @@ -298,7 +318,13 @@ private DecodeConfiguration getDecodeConfiguration(DecoderType decoderType, Site } } - + /** + * Loads the user selected site info into this editor. + * @param site to load + * @param system for the site + * @param systemInformation for the site + * @param decoder to lookup supplemental information + */ public void setSite(EnrichedSite site, System system, SystemInformation systemInformation, RadioReferenceDecoder decoder) { mCurrentSite = site; @@ -346,6 +372,29 @@ public void setSite(EnrichedSite site, System system, SystemInformation systemIn } else { + Flavor flavor = decoder.getFlavor(system); + + if(flavor != null && flavor.getName() != null && flavor.getName().equals(PHASE_2_FLAVOR)) + { + getP25ControlLabel().setVisible(true); + getTdmaControlToggleButton().setVisible(true); + getFdmaControlToggleButton().setVisible(true); + if(site.getSite().getModulation() != null && site.getSite().getModulation().contains(PHASE_2_TDMA_MODULATION)) + { + getTdmaControlToggleButton().setSelected(true); + } + else + { + getFdmaControlToggleButton().setSelected(true); + } + } + else + { + getP25ControlLabel().setVisible(false); + getTdmaControlToggleButton().setVisible(false); + getFdmaControlToggleButton().setVisible(false); + } + getCreateChannelConfigurationButton().setVisible(true); getGoToChannelEditorCheckBox().setVisible(true); getSystemTextField().setText(system.getName()); @@ -514,10 +563,13 @@ private void createControlChannel() DecoderType decoderType = mRadioReferenceDecoder.getDecoderType(mCurrentSystem); - //Change a phase 2 system to use the phase 1 control channel + //Phase 2 - inspect the site modulation and use Phase 2 for TDMA control channel, otherwise Phase 1 if(decoderType == DecoderType.P25_PHASE2) { - decoderType = DecoderType.P25_PHASE1; + if(getFdmaControlToggleButton().isSelected()) + { + decoderType = DecoderType.P25_PHASE1; + } } channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(), mCurrentSystemInformation)); @@ -587,10 +639,13 @@ private void createControlAndAlternatesChannel() DecoderType decoderType = mRadioReferenceDecoder.getDecoderType(mCurrentSystem); - //Change a phase 2 system to use the phase 1 control channel + //Phase 2 - inspect the site modulation and use Phase 2 for TDMA control channel, otherwise Phase 1 if(decoderType == DecoderType.P25_PHASE2) { - decoderType = DecoderType.P25_PHASE1; + if(getFdmaControlToggleButton().isSelected()) + { + decoderType = DecoderType.P25_PHASE1; + } } channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(), @@ -685,6 +740,16 @@ private void createChannels(boolean selectedOnly) Channel channel = getChannelTemplate(); DecoderType decoderType = mRadioReferenceDecoder.getDecoderType(mCurrentSystem); + + //Phase 2 - inspect the site modulation and use Phase 2 for TDMA control channel, otherwise Phase 1 + if(decoderType == DecoderType.P25_PHASE2) + { + if(getFdmaControlToggleButton().isSelected()) + { + decoderType = DecoderType.P25_PHASE1; + } + } + channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(), mCurrentSystemInformation)); @@ -750,6 +815,70 @@ private static long getFrequency(SiteFrequency siteFrequency) return (long)(siteFrequency.getFrequency() * 1E6); } + /** + * P25 Phase 2 control channel label. + */ + private Label getP25ControlLabel() + { + if(mP25ControlLabel == null) + { + mP25ControlLabel = new Label("Control"); + mP25ControlLabel.setVisible(false); + } + + return mP25ControlLabel; + } + + /** + * P25 Phase 2 control channel type (TDMA vs FDMA) selection buttons. + */ + private SegmentedButton getP25ControlSegmentedButton() + { + if(mP25ControlSegmentedButton == null) + { + mP25ControlSegmentedButton = new SegmentedButton(getFdmaControlToggleButton(), getTdmaControlToggleButton()); + mP25ControlSegmentedButton.getStyleClass().add(SegmentedButton.STYLE_CLASS_DARK); + mP25ControlSegmentedButton.getToggleGroup().selectedToggleProperty() + .addListener((observable, oldValue, newValue) -> { + //Ensure that one button is always selected + if(newValue == null) + { + oldValue.setSelected(true); + } + }); + } + + return mP25ControlSegmentedButton; + } + + /** + * P25 Phase 2 with FDMA (phase 1) control channel selection button. + */ + private ToggleButton getFdmaControlToggleButton() + { + if(mFdmaControlToggleButton == null) + { + mFdmaControlToggleButton = new ToggleButton("FDMA Phase 1"); + mFdmaControlToggleButton.setVisible(false); + } + + return mFdmaControlToggleButton; + } + + /** + * P25 Phase 2 with TDMA (phase 2) control channel selection button. + */ + private ToggleButton getTdmaControlToggleButton() + { + if(mTdmaControlToggleButton == null) + { + mTdmaControlToggleButton = new ToggleButton("TDMA Phase 2"); + mTdmaControlToggleButton.setVisible(false); + } + + return mTdmaControlToggleButton; + } + /** * Flashes the alias list combobox to let the user know that they must select an alias list */ diff --git a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemSiteSelectionEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemSiteSelectionEditor.java index a9bac122e..8be96c12d 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemSiteSelectionEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/radioreference/SystemSiteSelectionEditor.java @@ -22,6 +22,7 @@ package io.github.dsheirer.gui.playlist.radioreference; +import com.google.common.collect.Ordering; import io.github.dsheirer.playlist.PlaylistManager; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.rrapi.type.Flavor; @@ -42,6 +43,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collections; +import java.util.Comparator; import java.util.List; @@ -126,6 +129,7 @@ public void setSystem(System system, List sites, RadioReferenceDec getFlavorLabel().setText(flavor != null ? flavor.getName() : null); Voice voice = decoder.getVoice(system); getVoiceLabel().setText(voice != null ? voice.getName() : null); + Collections.sort(sites, Ordering.natural()); getSiteTableView().getItems().addAll(sites); setLoading(false); } @@ -212,27 +216,33 @@ private TableView getSiteTableView() mSiteTableView = new TableView<>(); mSiteTableView.setPlaceholder(getPlaceholderLabel()); - TableColumn numberColumn = new TableColumn(); - numberColumn.setText("Site"); - numberColumn.setCellValueFactory(new PropertyValueFactory<>("siteNumber")); - numberColumn.setPrefWidth(75); + TableColumn systemColumn = new TableColumn(); + systemColumn.setText("System"); + systemColumn.setCellValueFactory(new PropertyValueFactory<>("systemFormatted")); + systemColumn.setPrefWidth(60); TableColumn rfssColumn = new TableColumn(); rfssColumn.setText("RFSS"); - rfssColumn.setCellValueFactory(new PropertyValueFactory<>("rfss")); + rfssColumn.setCellValueFactory(new PropertyValueFactory<>("rfssFormatted")); rfssColumn.setPrefWidth(60); - TableColumn countyColumn = new TableColumn(); - countyColumn.setText("County"); - countyColumn.setCellValueFactory(new PropertyValueFactory<>("countyName")); - countyColumn.setPrefWidth(125); + TableColumn siteColumn = new TableColumn(); + siteColumn.setText("Site"); + siteColumn.setCellValueFactory(new PropertyValueFactory<>("siteFormatted")); + siteColumn.setPrefWidth(75); + + TableColumn countyNameColumn = new TableColumn(); + countyNameColumn.setText("County"); + countyNameColumn.setCellValueFactory(new PropertyValueFactory<>("countyName")); + countyNameColumn.setPrefWidth(125); TableColumn descriptionColumn = new TableColumn(); descriptionColumn.setText("Name"); descriptionColumn.setCellValueFactory(new PropertyValueFactory<>("description")); descriptionColumn.setPrefWidth(400); - mSiteTableView.getColumns().addAll(numberColumn, rfssColumn, countyColumn, descriptionColumn); + mSiteTableView.getColumns().addAll(systemColumn, rfssColumn, siteColumn, countyNameColumn, + descriptionColumn); mSiteTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); mSiteTableView.getSelectionModel().selectedItemProperty() .addListener((observable, oldValue, selected) -> diff --git a/src/main/java/io/github/dsheirer/gui/viewer/ChannelStartProcessingRequestViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/ChannelStartProcessingRequestViewer.java new file mode 100644 index 000000000..b97f61052 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/viewer/ChannelStartProcessingRequestViewer.java @@ -0,0 +1,234 @@ +/* + * ***************************************************************************** + * 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.gui.viewer; + +import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest; +import javafx.geometry.HPos; +import javafx.scene.control.Label; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; + +/** + * Viewer for channel start processing requests + */ +public class ChannelStartProcessingRequestViewer extends HBox +{ + private Label mChannelConfigLabel; + private Label mChannelDescriptorLabel; + private Label mTrafficChannelManagerLabel; + private Label mPreloadDataContentLabel; + private Label mParentDecodeHistoryLabel; + private Label mChildDecodeHistoryLabel; + private IdentifierCollectionViewer mIdentifierCollectionViewer; + + /** + * Constructs an instance + */ + public ChannelStartProcessingRequestViewer() + { + GridPane gridPane = new GridPane(); + gridPane.setVgap(5); + gridPane.setHgap(5); + + gridPane.add(new Label("Configuration:"), 0, 0); + GridPane.setHgrow(getChannelConfigLabel(), Priority.ALWAYS); + gridPane.add(getChannelConfigLabel(), 1, 0); + + gridPane.add(new Label("Channel:"), 0, 1); + GridPane.setHgrow(getChannelDescriptorLabel(), Priority.ALWAYS); + gridPane.add(getChannelDescriptorLabel(), 1, 1); + + gridPane.add(new Label("TCM:"), 0, 2); + GridPane.setHgrow(getTrafficChannelManagerLabel(), Priority.ALWAYS); + gridPane.add(getTrafficChannelManagerLabel(), 1, 2); + + gridPane.add(new Label("Preload Items:"), 0, 3); + GridPane.setHgrow(getPreloadDataContentLabel(), Priority.ALWAYS); + gridPane.add(getPreloadDataContentLabel(), 1, 3); + + gridPane.add(new Label("Parent History:"), 0, 4); + GridPane.setHgrow(getParentDecodeHistoryLabel(), Priority.ALWAYS); + gridPane.add(getParentDecodeHistoryLabel(), 1, 4); + + gridPane.add(new Label("Child History:"), 0, 5); + GridPane.setHgrow(getChildDecodeHistoryLabel(), Priority.ALWAYS); + gridPane.add(getChildDecodeHistoryLabel(), 1, 5); + + gridPane.add(new Label("Identifiers"), 0, 6); + GridPane.setHgrow(getIdentifierCollectionViewer(), Priority.ALWAYS); + getIdentifierCollectionViewer().setPrefHeight(120); + gridPane.add(getIdentifierCollectionViewer(), 0, 7, 2, 1); + + ColumnConstraints cc0 = new ColumnConstraints(); + cc0.setHalignment(HPos.RIGHT); + gridPane.getColumnConstraints().add(cc0); + + getChildren().add(gridPane); + } + + public void set(ChannelStartProcessingRequest request) + { + if(request != null) + { + if(request.getChannel() != null) + { + getChannelConfigLabel().setText(request.getChannel().toString()); + } + else + { + getChannelConfigLabel().setText("None"); + } + + if(request.getChannelDescriptor() != null) + { + getChannelDescriptorLabel().setText(request.getChannelDescriptor().toString() + " " + + request.getChannelDescriptor().getDownlinkFrequency() + " MHz"); + } + else + { + getChannelDescriptorLabel().setText("None"); + } + + if(request.getChildDecodeEventHistory() != null) + { + getChildDecodeHistoryLabel().setText(request.getChildDecodeEventHistory().getItems().size() + " items"); + } + else + { + getChildDecodeHistoryLabel().setText("None"); + } + + if(request.getParentDecodeEventHistory() != null) + { + getParentDecodeHistoryLabel().setText(request.getParentDecodeEventHistory().getItems().size() + " items"); + } + else + { + getParentDecodeHistoryLabel().setText("None"); + } + + if(request.getPreloadDataContents() != null) + { + getPreloadDataContentLabel().setText(request.getPreloadDataContents().size() + " items"); + } + else + { + getPreloadDataContentLabel().setText("None"); + } + + if(request.getTrafficChannelManager() != null) + { + getTrafficChannelManagerLabel().setText(request.getTrafficChannelManager().getClass().getName()); + } + else + { + getTrafficChannelManagerLabel().setText("None:"); + } + + if(request.getIdentifierCollection() != null) + { + getIdentifierCollectionViewer().set(request.getIdentifierCollection()); + } + } + else + { + getChannelDescriptorLabel().setText(null); + getChildDecodeHistoryLabel().setText(null); + getChannelConfigLabel().setText(null); + getIdentifierCollectionViewer().set(null); + getParentDecodeHistoryLabel().setText(null); + getPreloadDataContentLabel().setText(null); + getTrafficChannelManagerLabel().setText(null); + } + } + + public Label getChannelConfigLabel() + { + if(mChannelConfigLabel == null) + { + mChannelConfigLabel = new Label(); + } + + return mChannelConfigLabel; + } + + public Label getChannelDescriptorLabel() + { + if(mChannelDescriptorLabel == null) + { + mChannelDescriptorLabel = new Label(); + } + + return mChannelDescriptorLabel; + } + + private Label getTrafficChannelManagerLabel() + { + if(mTrafficChannelManagerLabel == null) + { + mTrafficChannelManagerLabel = new Label(); + + } + + return mTrafficChannelManagerLabel; + } + + private Label getPreloadDataContentLabel() + { + if(mPreloadDataContentLabel == null) + { + mPreloadDataContentLabel = new Label(); + } + + return mPreloadDataContentLabel; + } + + private Label getParentDecodeHistoryLabel() + { + if(mParentDecodeHistoryLabel == null) + { + mParentDecodeHistoryLabel = new Label(); + } + + return mParentDecodeHistoryLabel; + } + + private Label getChildDecodeHistoryLabel() + { + if(mChildDecodeHistoryLabel == null) + { + mChildDecodeHistoryLabel = new Label(); + } + + return mChildDecodeHistoryLabel; + } + + private IdentifierCollectionViewer getIdentifierCollectionViewer() + { + if(mIdentifierCollectionViewer == null) + { + mIdentifierCollectionViewer = new IdentifierCollectionViewer(); + } + + return mIdentifierCollectionViewer; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/viewer/IdentifierCollectionViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/IdentifierCollectionViewer.java new file mode 100644 index 000000000..27e5af6b7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/viewer/IdentifierCollectionViewer.java @@ -0,0 +1,90 @@ +/* + * ***************************************************************************** + * 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.gui.viewer; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +/** + * JavaFX identifier collection viewer + */ +public class IdentifierCollectionViewer extends VBox +{ + private TableView mIdentifierTableView; + + /** + * Constructs an instance + */ + public IdentifierCollectionViewer() + { + GridPane gridPane = new GridPane(); + GridPane.setHgrow(getIdentifierTableView(), Priority.ALWAYS); + gridPane.add(getIdentifierTableView(), 0, 0); + getChildren().add(gridPane); + } + + public void set(IdentifierCollection identifierCollection) + { + getIdentifierTableView().getItems().clear(); + + if(identifierCollection != null) + { + getIdentifierTableView().getItems().addAll(identifierCollection.getIdentifiers()); + } + } + + public TableView getIdentifierTableView() + { + if(mIdentifierTableView == null) + { + mIdentifierTableView = new TableView<>(); + + TableColumn classColumn = new TableColumn(); + classColumn.setPrefWidth(110); + classColumn.setText("Class"); + classColumn.setCellValueFactory(new PropertyValueFactory<>("identifierClass")); + + TableColumn formColumn = new TableColumn(); + formColumn.setPrefWidth(160); + formColumn.setText("Form"); + formColumn.setCellValueFactory(new PropertyValueFactory<>("form")); + + TableColumn roleColumn = new TableColumn(); + roleColumn.setPrefWidth(110); + roleColumn.setText("Role"); + roleColumn.setCellValueFactory(new PropertyValueFactory<>("role")); + + TableColumn valueColumn = new TableColumn(); + valueColumn.setPrefWidth(160); + valueColumn.setText("Value"); + valueColumn.setCellValueFactory(new PropertyValueFactory<>("value")); + + mIdentifierTableView.getColumns().addAll(classColumn, formColumn, roleColumn, valueColumn); + } + + return mIdentifierTableView; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/viewer/MessagePackage.java b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackage.java new file mode 100644 index 000000000..a912f88c8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackage.java @@ -0,0 +1,159 @@ +/* + * ***************************************************************************** + * 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.gui.viewer; + +import io.github.dsheirer.channel.state.DecoderStateEvent; +import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.event.DecodeEventSnapshot; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import java.util.ArrayList; +import java.util.List; + +/** + * Wrapper class that combines a single message with any decoder state events and decoded events that were produced by a + * decoder state. + */ +public class MessagePackage +{ + private IMessage mMessage; + private List mDecoderStateEvents = new ArrayList<>(); + private List mDecodeEvents = new ArrayList<>(); + private ChannelStartProcessingRequest mChannelStartProcessingRequest; + + /** + * Constructs an instance + * @param message for this instance + */ + public MessagePackage(IMessage message) + { + mMessage = message; + } + + /** + * Message for this instance + */ + public IMessage getMessage() + { + return mMessage; + } + + /** + * List of decoder state events + */ + public List getDecoderStateEvents() + { + return mDecoderStateEvents; + } + + /** + * Adds the decoder state event to this instance + */ + public void add(DecoderStateEvent event) + { + mDecoderStateEvents.add(event); + } + + /** + * List of decode event snapshots + */ + public List getDecodeEvents() + { + return mDecodeEvents; + } + + /** + * Adds the decode event to this instance + */ + public void add(DecodeEventSnapshot event) + { + mDecodeEvents.add(event); + } + + /** + * Adds the channel start processing request + */ + public void add(ChannelStartProcessingRequest request) + { + mChannelStartProcessingRequest = request; + } + + /** + * Message timeslot + */ + public int getTimeslot() + { + return getMessage().getTimeslot(); + } + + /** + * Message timestamp + */ + public long getTimestamp() + { + return getMessage().getTimestamp(); + } + + /** + * Message validity flag + */ + public boolean isValid() + { + return getMessage().isValid(); + } + + /** + * Message string representation + */ + @Override + public String toString() + { + return getMessage().toString(); + } + + /** + * Count of decode events + */ + public int getDecodeEventCount() + { + return mDecodeEvents.size(); + } + + /** + * Count of decoder state events + */ + public int getDecoderStateEventCount() + { + return mDecoderStateEvents.size(); + } + + /** + * Count (0 or 1) of channel start processing requests. + */ + public int getChannelStartProcessingRequestCount() + { + return mChannelStartProcessingRequest == null ? 0 : 1; + } + + public ChannelStartProcessingRequest getChannelStartProcessingRequest() + { + return mChannelStartProcessingRequest; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/viewer/MessagePackageViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackageViewer.java new file mode 100644 index 000000000..061c71c05 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackageViewer.java @@ -0,0 +1,225 @@ +/* + * ***************************************************************************** + * 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.gui.viewer; + +import io.github.dsheirer.channel.state.DecoderStateEvent; +import io.github.dsheirer.module.decode.event.DecodeEventSnapshot; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +/** + * JavaFX message package details viewer + */ +public class MessagePackageViewer extends VBox +{ + private Label mMessageLabel; + private TableView mDecoderStateEventTableView; + private TableView mDecodeEventTableView; + private IdentifierCollectionViewer mIdentifierCollectionViewer; + private ChannelStartProcessingRequestViewer mChannelStartProcessingRequestViewer; + + /** + * Constructs an instance + */ + public MessagePackageViewer() + { + GridPane gridPane = new GridPane(); + gridPane.setHgap(5); + gridPane.setVgap(5); + Label messageLabel = new Label("Message:"); + gridPane.add(messageLabel, 0, 0); + GridPane.setHgrow(getMessageLabel(), Priority.ALWAYS); + gridPane.add(getMessageLabel(), 1, 0); + + gridPane.add(new Label("Decoder State Events"), 0, 1); + getDecoderStateEventTableView().setPrefHeight(120); + GridPane.setHgrow(getDecoderStateEventTableView(), Priority.NEVER); + gridPane.add(getDecoderStateEventTableView(), 0, 2); + + gridPane.add(new Label("Decode Events"), 1, 1); + getDecodeEventTableView().setPrefHeight(120); + GridPane.setHgrow(getDecodeEventTableView(), Priority.ALWAYS); + gridPane.add(getDecodeEventTableView(), 1, 2); + + gridPane.add(new Label("Channel Start Processing Request"), 0, 3); + gridPane.add(getChannelStartProcessingRequestViewer(), 0, 4); + + getIdentifierCollectionViewer().setPrefHeight(120); + gridPane.add(new Label("Selected Decode Event Identifiers"), 1, 3); + gridPane.add(getIdentifierCollectionViewer(), 1, 4); + + getChildren().add(gridPane); + } + + public void set(MessagePackage messagePackage) + { + getMessageLabel().setText(null); + getDecoderStateEventTableView().getItems().clear(); + getDecodeEventTableView().getItems().clear(); + getChannelStartProcessingRequestViewer().set(null); + + if(messagePackage != null) + { + String message = messagePackage.toString(); + if(message.length() > 40) + { + message = message.substring(0, 40) + "..."; + } + getMessageLabel().setText(message); + getDecoderStateEventTableView().getItems().addAll(messagePackage.getDecoderStateEvents()); + getDecodeEventTableView().getItems().addAll(messagePackage.getDecodeEvents()); + getChannelStartProcessingRequestViewer().set(messagePackage.getChannelStartProcessingRequest()); + + if(getDecodeEventTableView().getItems().size() > 0) + { + getDecodeEventTableView().getSelectionModel().select(0); + } + } + } + + private IdentifierCollectionViewer getIdentifierCollectionViewer() + { + if(mIdentifierCollectionViewer == null) + { + mIdentifierCollectionViewer = new IdentifierCollectionViewer(); + } + + return mIdentifierCollectionViewer; + } + + private Label getMessageLabel() + { + if(mMessageLabel == null) + { + mMessageLabel = new Label(); + mMessageLabel.setMaxWidth(Double.MAX_VALUE); + } + + return mMessageLabel; + } + + public TableView getDecoderStateEventTableView() + { + if(mDecoderStateEventTableView == null) + { + mDecoderStateEventTableView = new TableView<>(); + + TableColumn timeslotColumn = new TableColumn(); + timeslotColumn.setPrefWidth(110); + timeslotColumn.setText("Timeslot"); + timeslotColumn.setCellValueFactory(new PropertyValueFactory<>("timeslot")); + + TableColumn stateColumn = new TableColumn(); + stateColumn.setPrefWidth(110); + stateColumn.setText("State"); + stateColumn.setCellValueFactory(new PropertyValueFactory<>("state")); + + TableColumn eventColumn = new TableColumn(); + eventColumn.setPrefWidth(110); + eventColumn.setText("Event"); + eventColumn.setCellValueFactory(new PropertyValueFactory<>("event")); + + TableColumn frequencyColumn = new TableColumn(); + frequencyColumn.setPrefWidth(110); + frequencyColumn.setText("Frequency"); + frequencyColumn.setCellValueFactory(new PropertyValueFactory<>("frequency")); + + mDecoderStateEventTableView.getColumns().addAll(timeslotColumn, stateColumn, eventColumn, frequencyColumn); + } + + return mDecoderStateEventTableView; + } + + private TableView getDecodeEventTableView() + { + if(mDecodeEventTableView == null) + { + mDecodeEventTableView = new TableView<>(); + mDecodeEventTableView.setMaxWidth(Double.MAX_VALUE); + + TableColumn startTimeColumn = new TableColumn(); + startTimeColumn.setPrefWidth(110); + startTimeColumn.setText("Start"); + startTimeColumn.setCellValueFactory(new PropertyValueFactory<>("timeStart")); + + TableColumn durationColumn = new TableColumn(); + durationColumn.setPrefWidth(110); + durationColumn.setText("Duration"); + durationColumn.setCellValueFactory(new PropertyValueFactory<>("duration")); + + TableColumn typeColumn = new TableColumn(); + typeColumn.setPrefWidth(130); + typeColumn.setText("Type"); + typeColumn.setCellValueFactory(new PropertyValueFactory<>("eventType")); + + TableColumn channelDescriptorColumn = new TableColumn(); + channelDescriptorColumn.setPrefWidth(110); + channelDescriptorColumn.setText("Channel"); + channelDescriptorColumn.setCellValueFactory(new PropertyValueFactory<>("channelDescriptor")); + + TableColumn frequencyColumn = new TableColumn(); + frequencyColumn.setPrefWidth(110); + frequencyColumn.setText("Frequency"); + frequencyColumn.setCellValueFactory(new PropertyValueFactory<>("frequency")); + + TableColumn hashcodeColumn = new TableColumn(); + hashcodeColumn.setPrefWidth(100); + hashcodeColumn.setText("Hash ID"); + hashcodeColumn.setCellValueFactory(new PropertyValueFactory<>("originalHashCode")); + + TableColumn detailsColumn = new TableColumn(); + detailsColumn.setPrefWidth(500); + detailsColumn.setText("Details"); + detailsColumn.setCellValueFactory(new PropertyValueFactory<>("details")); + + mDecodeEventTableView.getColumns().addAll(startTimeColumn, durationColumn, typeColumn, channelDescriptorColumn, + frequencyColumn, hashcodeColumn, detailsColumn); + + mDecodeEventTableView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> + { + if(newValue != null) + { + getIdentifierCollectionViewer().set(newValue.getIdentifierCollection()); + } + else + { + getIdentifierCollectionViewer().set(null); + } + }); + } + + return mDecodeEventTableView; + } + + private ChannelStartProcessingRequestViewer getChannelStartProcessingRequestViewer() + { + if(mChannelStartProcessingRequestViewer == null) + { + mChannelStartProcessingRequestViewer = new ChannelStartProcessingRequestViewer(); + } + + return mChannelStartProcessingRequestViewer; + } +} diff --git a/src/main/java/io/github/dsheirer/gui/viewer/MessagePackager.java b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackager.java new file mode 100644 index 000000000..ef5fb0744 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackager.java @@ -0,0 +1,107 @@ +/* + * ***************************************************************************** + * 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.gui.viewer; + +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.channel.state.DecoderStateEvent; +import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventSnapshot; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEventListener; + +/** + * Utility for combining a message and decoder state events. + */ +public class MessagePackager +{ + private MessagePackage mMessagePackage; + + /** + * Constructs an instance + */ + public MessagePackager() + { + } + + /** + * Adds the message and creates a new MessageWithEvents instance, wrapping the message, ready to also receive any + * decode events and decoder state events. The previous message with events is overwritten. + * @param message to wrap. + */ + public void add(IMessage message) + { + mMessagePackage = new MessagePackage(message); + } + + /** + * Access the current message with events. + */ + public MessagePackage getMessageWithEvents() + { + return mMessagePackage; + } + + /** + * Adds the decoder state event to the current message with events. + * @param event to add + */ + public void add(DecoderStateEvent event) + { + if(mMessagePackage != null) + { + mMessagePackage.add(event); + } + } + + /** + * Adds the decode event to the current message with events. + * @param event to add + */ + public void add(IDecodeEvent event) + { + if(mMessagePackage != null) + { + if(event instanceof DecodeEvent decodeEvent) + { + try + { + DecodeEventSnapshot snapshot = decodeEvent.getSnapshot(); + mMessagePackage.add(snapshot); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + } + + /** + * Subscription to receive channel start processing requests via the event bus from the traffic channel manager + * @param request sent from the traffic channel manager. + */ + @Subscribe + public void process(ChannelStartProcessingRequest request) + { + mMessagePackage.add(request); + } +} diff --git a/src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/MessageRecordingViewer.java similarity index 86% rename from src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java rename to src/main/java/io/github/dsheirer/gui/viewer/MessageRecordingViewer.java index c3a4952c7..6729fb1a4 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/MessageRecordingViewer.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -41,20 +41,21 @@ /** * Utility application to load and view .bits recording file with the messages fully parsed. * - * Supported Protocols: DMR and APCO25 Phase 1. + * Supported Protocols: DMR, APCO25 Phase 1 and Phase 2. */ -public class RecordingViewer extends VBox +public class MessageRecordingViewer extends VBox { - private static final Logger mLog = LoggerFactory.getLogger(RecordingViewer.class); + private static final Logger mLog = LoggerFactory.getLogger(MessageRecordingViewer.class); private MenuBar mMenuBar; private TabPane mTabPane; private int mTabCounterDmr = 1; private int mTabCounterP25P1 = 1; + private int mTabCounterP25P2 = 1; /** * Constructs an instance */ - public RecordingViewer() + public MessageRecordingViewer() { VBox.setVgrow(getTabPane(), Priority.ALWAYS); getChildren().addAll(getMenuBar(), getTabPane()); @@ -74,13 +75,19 @@ public MenuBar getMenuBar() getTabPane().getTabs().add(tab); getTabPane().getSelectionModel().select(tab); }); - MenuItem p25p1MenuItem = new MenuItem("P25P1"); + MenuItem p25p1MenuItem = new MenuItem("P25 Phase 1"); p25p1MenuItem.onActionProperty().set(event -> { Tab tab = new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer()); getTabPane().getTabs().add(tab); getTabPane().getSelectionModel().select(tab); }); - createNewViewerMenu.getItems().addAll(dmrMenuItem, p25p1MenuItem); + MenuItem p25p2MenuItem = new MenuItem("P25 Phase 2"); + p25p2MenuItem.onActionProperty().set(event -> { + Tab tab = new LabeledTab("P25P2-" + mTabCounterP25P2++, new P25P2Viewer()); + getTabPane().getTabs().add(tab); + getTabPane().getSelectionModel().select(tab); + }); + createNewViewerMenu.getItems().addAll(dmrMenuItem, p25p1MenuItem, p25p2MenuItem); MenuItem exitMenu = new MenuItem("Exit"); exitMenu.onActionProperty().set(event -> ((Stage)getScene().getWindow()).close()); @@ -102,6 +109,7 @@ public TabPane getTabPane() mTabPane.setMaxHeight(Double.MAX_VALUE); mTabPane.getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer())); mTabPane.getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer())); + mTabPane.getTabs().add(new LabeledTab("P25P2-" + mTabCounterP25P2++, new P25P2Viewer())); } return mTabPane; @@ -151,11 +159,6 @@ public LabeledTab(String label, Node node) } }); } - - public LabeledTab(String label) - { - this(label, null); - } } public static void main(String[] args) @@ -165,7 +168,7 @@ public static void main(String[] args) @Override public void start(Stage primaryStage) throws Exception { - Scene scene = new Scene(new RecordingViewer(), 1100, 800); + Scene scene = new Scene(new MessageRecordingViewer(), 1100, 800); primaryStage.setTitle("Message Recording Viewer (.bits)"); primaryStage.setScene(scene); primaryStage.show(); diff --git a/src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java b/src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java index 997761e70..d50c9d555 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -19,8 +19,14 @@ package io.github.dsheirer.gui.viewer; -import io.github.dsheirer.message.IMessage; +import com.google.common.eventbus.EventBus; +import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; import io.github.dsheirer.message.StuffBitsMessage; +import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager; +import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderState; import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageFramer; import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageProcessor; import io.github.dsheirer.record.binary.BinaryReader; @@ -33,8 +39,11 @@ import java.util.TreeSet; import java.util.function.Predicate; import java.util.prefs.Preferences; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -71,17 +80,20 @@ public class P25P1Viewer extends VBox private static final Logger mLog = LoggerFactory.getLogger(P25P1Viewer.class); private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY); private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.p25p1"; + private static final String FILE_FREQUENCY_REGEX = ".*\\d{8}_\\d{6}_(\\d{9}).*"; private Preferences mPreferences = Preferences.userNodeForPackage(P25P1Viewer.class); private Button mSelectFileButton; private Label mSelectedFileLabel; - private TableView mMessageTableView; - private ObservableList mMessages = FXCollections.observableArrayList(); - private FilteredList mFilteredMessages = new FilteredList<>(mMessages); + private TableView mMessagePackageTableView; + private ObservableList mMessagePackages = FXCollections.observableArrayList(); + private FilteredList mFilteredMessagePackages = new FilteredList<>(mMessagePackages); private TextField mSearchText; private TextField mFindText; private Button mFindButton; private Button mFindNextButton; private ProgressIndicator mLoadingIndicator; + private MessagePackageViewer mMessagePackageViewer; + private StringProperty mLoadedFile = new SimpleStringProperty(); public P25P1Viewer() { @@ -115,9 +127,11 @@ public P25P1Viewer() VBox.setVgrow(fileBox, Priority.NEVER); VBox.setVgrow(filterBox, Priority.NEVER); - VBox.setVgrow(getMessageTableView(), Priority.ALWAYS); + VBox.setVgrow(getMessagePackageTableView(), Priority.ALWAYS); + VBox.setVgrow(getMessagePackageViewer(), Priority.NEVER); + + getChildren().addAll(fileBox, filterBox, getMessagePackageTableView(), getMessagePackageViewer()); - getChildren().addAll(fileBox, filterBox, getMessageTableView()); } /** @@ -132,19 +146,54 @@ private void load(File file) { if(file != null && file.exists()) { - mMessages.clear(); + mLoadedFile.set(file.toString()); + mMessagePackages.clear(); getLoadingIndicator().setVisible(true); getSelectedFileLabel().setText("Loading ..."); ThreadPool.CACHED.submit(() -> { - List messages = new ArrayList<>(); + List messagePackages = new ArrayList<>(); P25P1MessageFramer messageFramer = new P25P1MessageFramer(null, 9600); P25P1MessageProcessor messageProcessor = new P25P1MessageProcessor(); messageFramer.setListener(messageProcessor); + + Channel empty = new Channel("Empty"); + empty.setDecodeConfiguration(new DecodeConfigP25Phase1()); + + MessagePackager messagePackager = new MessagePackager(); + + //Setup a temporary event bus to capture channel start processing requests + EventBus eventBus = new EventBus("debug"); + eventBus.register(messagePackager); + P25TrafficChannelManager trafficChannelManager = new P25TrafficChannelManager(empty); + trafficChannelManager.setInterModuleEventBus(eventBus); + + //Register to receive events + trafficChannelManager.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent)); + P25P1DecoderState decoderState = new P25P1DecoderState(empty, trafficChannelManager); + decoderState.setDecoderStateListener(decoderStateEvent -> messagePackager.add(decoderStateEvent)); + decoderState.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent)); + decoderState.start(); + + long frequency = getFrequencyFromFile(mLoadedFile.get()); + + if(frequency > 0) + { + trafficChannelManager.setCurrentControlFrequency(frequency, empty); + FrequencyConfigurationIdentifier id = FrequencyConfigurationIdentifier.create(frequency); + decoderState.getConfigurationIdentifierListener().receive(new IdentifierUpdateNotification(id, + IdentifierUpdateNotification.Operation.ADD, 1)); + } + messageProcessor.setMessageListener(message -> { if(!(message instanceof StuffBitsMessage)) { - messages.add(message); + //Add the initial message to the packager so that it can be combined with any decoder state events. + messagePackager.add(message); + decoderState.receive(message); + + //Collect the packaged message with events + messagePackages.add(messagePackager.getMessageWithEvents()); } }); @@ -164,13 +213,47 @@ private void load(File file) Platform.runLater(() -> { getLoadingIndicator().setVisible(false); getSelectedFileLabel().setText(file.getName()); - mMessages.addAll(messages); - getMessageTableView().scrollTo(0); + mMessagePackages.addAll(messagePackages); + getMessagePackageTableView().scrollTo(0); }); }); } } + /** + * Extracts the channel frequency value from the bits file name to broadcast as the current frequency for each of + * the decoder states. + * @param file name to parse + * @return parsed frequency or zero. + */ + private static long getFrequencyFromFile(String file) + { + if(file == null || file.isEmpty()) + { + return 0; + } + + if(file.matches(FILE_FREQUENCY_REGEX)) + { + Pattern p = Pattern.compile(FILE_FREQUENCY_REGEX); + Matcher m = p.matcher(file); + if(m.find()) + { + try + { + String raw = m.group(1); + return Long.parseLong(raw); + } + catch(Exception e) + { + mLog.error("Couldn't parse frequency from bits file [" + file + "]"); + } + } + } + + return 0; + } + /** * Updates the filter(s) applies to the list of messages */ @@ -180,12 +263,12 @@ private void updateFilters() if(filterText != null && !filterText.isEmpty()) { - Predicate textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase()); - mFilteredMessages.setPredicate(textPredicate); + Predicate textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase()); + mFilteredMessagePackages.setPredicate(textPredicate); } else { - mFilteredMessages.setPredicate(null); + mFilteredMessagePackages.setPredicate(null); } } @@ -197,12 +280,12 @@ private void find(String text) { if(text != null && !text.isEmpty()) { - for(IMessage message: mFilteredMessages) + for(MessagePackage messagePackage: mFilteredMessagePackages) { - if(message.toString().toLowerCase().contains(text.toLowerCase())) + if(messagePackage.toString().toLowerCase().contains(text.toLowerCase())) { - getMessageTableView().getSelectionModel().select(message); - getMessageTableView().scrollTo(message); + getMessagePackageTableView().getSelectionModel().select(messagePackage); + getMessagePackageTableView().scrollTo(messagePackage); return; } } @@ -217,7 +300,7 @@ private void findNext(String text) { if(text != null && !text.isEmpty()) { - IMessage selected = getMessageTableView().getSelectionModel().getSelectedItem(); + MessagePackage selected = getMessagePackageTableView().getSelectionModel().getSelectedItem(); if(selected == null) { @@ -225,18 +308,18 @@ private void findNext(String text) return; } - int row = mFilteredMessages.indexOf(selected); + int row = mFilteredMessagePackages.indexOf(selected); - for(int x = row + 1; x < mFilteredMessages.size(); x++) + for(int x = row + 1; x < mFilteredMessagePackages.size(); x++) { - if(x < mFilteredMessages.size()) + if(x < mFilteredMessagePackages.size()) { - IMessage message = mFilteredMessages.get(x); + MessagePackage messagePackage = mFilteredMessagePackages.get(x); - if(message.toString().toLowerCase().contains(text.toLowerCase())) + if(messagePackage.toString().toLowerCase().contains(text.toLowerCase())) { - getMessageTableView().getSelectionModel().select(message); - getMessageTableView().scrollTo(message); + getMessagePackageTableView().getSelectionModel().select(messagePackage); + getMessagePackageTableView().scrollTo(messagePackage); return; } } @@ -244,25 +327,40 @@ private void findNext(String text) } } + private MessagePackageViewer getMessagePackageViewer() + { + if(mMessagePackageViewer == null) + { + mMessagePackageViewer = new MessagePackageViewer(); + mMessagePackageViewer.setMaxWidth(Double.MAX_VALUE); + + //Register for table selection events to display the selected value. + getMessagePackageTableView().getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> getMessagePackageViewer().set(newValue)); + } + + return mMessagePackageViewer; + } + /** - * List view control with DMR messages + * List view control with messages */ - private TableView getMessageTableView() + private TableView getMessagePackageTableView() { - if(mMessageTableView == null) + if(mMessagePackageTableView == null) { - mMessageTableView = new TableView<>(); - mMessageTableView.setPlaceholder(getLoadingIndicator()); - SortedList sortedList = new SortedList<>(mFilteredMessages); - sortedList.comparatorProperty().bind(mMessageTableView.comparatorProperty()); - mMessageTableView.setItems(sortedList); + mMessagePackageTableView = new TableView<>(); + mMessagePackageTableView.setPlaceholder(getLoadingIndicator()); + SortedList sortedList = new SortedList<>(mFilteredMessagePackages); + sortedList.comparatorProperty().bind(mMessagePackageTableView.comparatorProperty()); + mMessagePackageTableView.setItems(sortedList); - mMessageTableView.setOnKeyPressed(event -> + mMessagePackageTableView.setOnKeyPressed(event -> { if(KEY_CODE_COPY.match(event)) { final Set rows = new TreeSet<>(); - for (final TablePosition tablePosition : mMessageTableView.getSelectionModel().getSelectedCells()) + for (final TablePosition tablePosition : mMessagePackageTableView.getSelectionModel().getSelectedCells()) { rows.add(tablePosition.getRow()); } @@ -282,7 +380,7 @@ private TableView getMessageTableView() boolean firstCol = true; - for (final TableColumn column : mMessageTableView.getColumns()) + for (final TableColumn column : mMessagePackageTableView.getColumns()) { if(firstCol) { @@ -313,23 +411,44 @@ private TableView getMessageTableView() validColumn.setText("Valid"); validColumn.setCellValueFactory(new PropertyValueFactory<>("valid")); + TableColumn timeslotColumn = new TableColumn(); + timeslotColumn.setPrefWidth(35); + timeslotColumn.setText("TS"); + timeslotColumn.setCellValueFactory(new PropertyValueFactory<>("timeslot")); + TableColumn messageColumn = new TableColumn(); - messageColumn.setPrefWidth(1000); + messageColumn.setPrefWidth(900); messageColumn.setText("Message"); messageColumn.setCellValueFactory((Callback) param -> { SimpleStringProperty property = new SimpleStringProperty(); - if(param.getValue() instanceof IMessage message) + if(param.getValue() instanceof MessagePackage messagePackage) { - property.set(message.toString()); + property.set(messagePackage.toString()); } return property; }); - mMessageTableView.getColumns().addAll(timestampColumn, validColumn, messageColumn); + TableColumn decodeEventCountColumn = new TableColumn(); + decodeEventCountColumn.setPrefWidth(50); + decodeEventCountColumn.setText("Events"); + decodeEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decodeEventCount")); + + TableColumn decoderStateEventCountColumn = new TableColumn(); + decoderStateEventCountColumn.setPrefWidth(50); + decoderStateEventCountColumn.setText("States"); + decoderStateEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decoderStateEventCount")); + + TableColumn channelStartCountColumn = new TableColumn(); + channelStartCountColumn.setPrefWidth(50); + channelStartCountColumn.setText("Starts"); + channelStartCountColumn.setCellValueFactory(new PropertyValueFactory<>("channelStartProcessingRequestCount")); + + mMessagePackageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn, + decodeEventCountColumn, decoderStateEventCountColumn, channelStartCountColumn); } - return mMessageTableView; + return mMessagePackageTableView; } /** @@ -343,7 +462,7 @@ private Button getSelectFileButton() mSelectFileButton = new Button("Select ..."); mSelectFileButton.onActionProperty().set(event -> { FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("Select DMR .bits Recording"); + fileChooser.setTitle("Select P25 Phase 1 .bits Recording"); String lastDirectory = mPreferences.get(LAST_SELECTED_DIRECTORY, null); if(lastDirectory != null) { diff --git a/src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java b/src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java new file mode 100644 index 000000000..94723b6b8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java @@ -0,0 +1,738 @@ +/* + * ***************************************************************************** + * 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.gui.viewer; + +import com.google.common.eventbus.EventBus; +import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.gui.control.IntegerTextField; +import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; +import io.github.dsheirer.identifier.patch.PatchGroupManager; +import io.github.dsheirer.message.StuffBitsMessage; +import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent; +import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager; +import io.github.dsheirer.module.decode.p25.phase1.message.P25FrequencyBand; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; +import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2; +import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState; +import io.github.dsheirer.module.decode.p25.phase2.P25P2MessageFramer; +import io.github.dsheirer.module.decode.p25.phase2.P25P2MessageProcessor; +import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; +import io.github.dsheirer.record.binary.BinaryReader; +import io.github.dsheirer.util.ThreadPool; +import java.io.File; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Predicate; +import java.util.prefs.Preferences; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javafx.util.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * APCO25 Phase 2 viewer panel + */ +public class P25P2Viewer extends VBox +{ + private static final Logger mLog = LoggerFactory.getLogger(P25P2Viewer.class); + private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY); + private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.p25p2"; + private static final String FILE_FREQUENCY_REGEX = ".*\\d{8}_\\d{6}_(\\d{9}).*"; + private Preferences mPreferences = Preferences.userNodeForPackage(P25P2Viewer.class); + private Button mSelectFileButton; + private Label mSelectedFileLabel; + private TableView mMessagePackageTableView; + private ObservableList mMessagePackages = FXCollections.observableArrayList(); + private FilteredList mFilteredMessagePackages = new FilteredList<>(mMessagePackages); + private CheckBox mShowTS0; + private CheckBox mShowTS1; + private CheckBox mShowTS2; + private TextField mSearchText; + private TextField mFindText; + private Button mFindButton; + private Button mFindNextButton; + private ProgressIndicator mLoadingIndicator; + private IntegerTextField mWACNTextField; + private IntegerTextField mSystemTextField; + private IntegerTextField mNACTextField; + private Button mReloadButton; + private StringProperty mLoadedFile = new SimpleStringProperty(); + private MessagePackageViewer mMessagePackageViewer; + + public P25P2Viewer() + { + setPadding(new Insets(5)); + setSpacing(5); + + HBox fileBox = new HBox(); + fileBox.setMaxWidth(Double.MAX_VALUE); + fileBox.setAlignment(Pos.CENTER_LEFT); + fileBox.setSpacing(5); + getSelectedFileLabel().setAlignment(Pos.BASELINE_CENTER); + + HBox.setHgrow(getSelectFileButton(), Priority.NEVER); + HBox.setHgrow(getSelectedFileLabel(), Priority.ALWAYS); + fileBox.getChildren().addAll(getSelectFileButton(), getSelectedFileLabel()); + + HBox scrambleSettingsBox = new HBox(); + scrambleSettingsBox.setAlignment(Pos.BASELINE_LEFT); + scrambleSettingsBox.setSpacing(5); + Label wacnLabel = new Label("WACN:"); + Label systemLabel = new Label("SYSTEM:"); + Label nacLabel = new Label("NAC:"); + scrambleSettingsBox.getChildren().addAll(wacnLabel, getWACNTextField(), systemLabel, getSystemTextField(), + nacLabel, getNACTextField(), getReloadButton()); + + HBox filterBox = new HBox(); + filterBox.setMaxWidth(Double.MAX_VALUE); + filterBox.setAlignment(Pos.BASELINE_CENTER); + filterBox.setSpacing(5); + + Label searchLabel = new Label("Message Filter:"); + HBox.setMargin(searchLabel, new Insets(0,0,0,15)); + Label findLabel = new Label("Find:"); + + HBox.setHgrow(getFindText(), Priority.ALWAYS); + HBox.setHgrow(getSearchText(), Priority.ALWAYS); + + Label showLabel = new Label("Show:"); + HBox.setMargin(showLabel, new Insets(0,0,0,15)); + + filterBox.getChildren().addAll(findLabel, getFindText(), getFindButton(), getFindNextButton(), searchLabel, + getSearchText(), showLabel, getShowTS0(), getShowTS1(), getShowTS2()); + + VBox.setVgrow(fileBox, Priority.NEVER); + VBox.setVgrow(filterBox, Priority.NEVER); + VBox.setVgrow(scrambleSettingsBox, Priority.NEVER); + VBox.setVgrow(getMessagePackageTableView(), Priority.ALWAYS); + VBox.setVgrow(getMessagePackageViewer(), Priority.NEVER); + + getChildren().addAll(fileBox, scrambleSettingsBox, filterBox, getMessagePackageTableView(), getMessagePackageViewer()); + } + + /** + * Extracts the channel frequency value from the bits file name to broadcast as the current frequency for each of + * the decoder states. + * @param file name to parse + * @return parsed frequency or zero. + */ + private static long getFrequencyFromFile(String file) + { + if(file == null || file.isEmpty()) + { + return 0; + } + + if(file.matches(FILE_FREQUENCY_REGEX)) + { + Pattern p = Pattern.compile(FILE_FREQUENCY_REGEX); + Matcher m = p.matcher(file); + if(m.find()) + { + try + { + String raw = m.group(1); + return Long.parseLong(raw); + } + catch(Exception e) + { + mLog.error("Couldn't parse frequency from bits file [" + file + "]"); + } + } + } + + return 0; + } + + /** + * Processes the recording file and loads the content into the viewer + * + * Note: invoke this method off of the UI thread in a thread pool executor and the results will be loaded into the + * message table back on the JavaFX UI thread. + * + * @param file containing a .bits recording of decoded data. + */ + private void load(File file) + { + mLog.info("Loading File: " + file); + if(file != null && file.exists()) + { + mLoadedFile.set(file.toString()); + mMessagePackages.clear(); + getLoadingIndicator().setVisible(true); + getSelectedFileLabel().setText("Loading ..."); + + int wacn = getWACNTextField().get(); + int system = getSystemTextField().get(); + int nac = getNACTextField().get(); + ScrambleParameters scrambleParameters = new ScrambleParameters(wacn, system, nac); + + ThreadPool.CACHED.submit(() -> { + List messages = new ArrayList<>(); + P25P2MessageFramer messageFramer = new P25P2MessageFramer(null, 9600); + messageFramer.setScrambleParameters(scrambleParameters); + P25P2MessageProcessor messageProcessor = new P25P2MessageProcessor(); + messageFramer.setListener(messageProcessor); + Channel empty = new Channel("Empty"); + empty.setDecodeConfiguration(new DecodeConfigP25Phase2()); + + MessagePackager messagePackager = new MessagePackager(); + + //Setup a temporary event bus to capture channel start processing requests + EventBus eventBus = new EventBus("debug"); + eventBus.register(messagePackager); + P25TrafficChannelManager trafficChannelManager = new P25TrafficChannelManager(empty); + trafficChannelManager.setInterModuleEventBus(eventBus); + + //Register to receive events + trafficChannelManager.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent)); + PatchGroupManager patchGroupManager = new PatchGroupManager(); + P25P2DecoderState decoderState1 = new P25P2DecoderState(empty, 1, trafficChannelManager, + patchGroupManager); + decoderState1.setDecoderStateListener(decoderStateEvent -> messagePackager.add(decoderStateEvent)); + decoderState1.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent)); + P25P2DecoderState decoderState2 = new P25P2DecoderState(empty, 2, trafficChannelManager, + patchGroupManager); + decoderState2.setDecoderStateListener(decoderStateEvent -> messagePackager.add(decoderStateEvent)); + decoderState2.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent)); + decoderState1.start(); + decoderState2.start(); + + long frequency = getFrequencyFromFile(mLoadedFile.get()); + + if(frequency > 0) + { + trafficChannelManager.setCurrentControlFrequency(frequency, empty); + FrequencyConfigurationIdentifier id = FrequencyConfigurationIdentifier.create(frequency); + decoderState1.getConfigurationIdentifierListener().receive(new IdentifierUpdateNotification(id, + IdentifierUpdateNotification.Operation.ADD, 1)); + decoderState2.getConfigurationIdentifierListener().receive(new IdentifierUpdateNotification(id, + IdentifierUpdateNotification.Operation.ADD, 2)); + } + + //TODO: testing use - PCWIN TDMA Frequency Band as preload data + //ID:2 OFFSET:-45000000 SPACING:12500 BASE:851012500 TDMA BW:12500 TIMESLOTS:2 VOCODER:HALF_RATE + P25FrequencyBand band = new P25FrequencyBand(2, 851012500l, -45000000l, 12500, 12500, 2); + P25FrequencyBandPreloadDataContent content = new P25FrequencyBandPreloadDataContent(Collections.singleton(band)); + messageProcessor.preload(content); + + messageProcessor.setMessageListener(message -> { + if(!(message instanceof StuffBitsMessage)) + { +// System.out.println(message); + //Add the initial message to the packager so that it can be combined with any decoder state events. + messagePackager.add(message); + if(message.getTimeslot() == P25P1Message.TIMESLOT_1) + { + decoderState1.receive(message); + } + else if(message.getTimeslot() == P25P1Message.TIMESLOT_2) + { + decoderState2.receive(message); + } + + //Collect the packaged message with events + messages.add(messagePackager.getMessageWithEvents()); + } + }); + + try(BinaryReader reader = new BinaryReader(file.toPath(), 200)) + { + while(reader.hasNext()) + { + ByteBuffer buffer = reader.next(); +// System.out.println("Processing Bytes " + buffer.capacity() + " / " + reader.getByteCounter()); + messageFramer.receive(buffer); + } + } + catch(Exception ioe) + { + ioe.printStackTrace(); + } + + Platform.runLater(() -> { + getLoadingIndicator().setVisible(false); + getSelectedFileLabel().setText(file.getName()); + mMessagePackages.addAll(messages); + getMessagePackageTableView().scrollTo(0); + }); + }); + } + else + { + mLog.info("Can't load file: " + file); + } + } + + /** + * Updates the filter(s) applies to the list of messages + */ + private void updateFilters() + { + Predicate timeslotPredicate = message -> + (getShowTS0().isSelected() && (message.getTimeslot() == 0)) || + (getShowTS1().isSelected() && (message.getTimeslot() == 1)) || + (getShowTS2().isSelected() && (message.getTimeslot() == 2)); + + String filterText = getSearchText().getText(); + + if(filterText == null || filterText.isEmpty()) + { + mFilteredMessagePackages.setPredicate(timeslotPredicate); + } + else + { + Predicate textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase()); + mFilteredMessagePackages.setPredicate(timeslotPredicate.and(textPredicate)); + } + } + + /** + * Finds and selects the first row containing the text argument. + * @param text to search for. + */ + private void find(String text) + { + if(text != null && !text.isEmpty()) + { + for(MessagePackage messagePackage: mFilteredMessagePackages) + { + if(messagePackage.toString().toLowerCase().contains(text.toLowerCase())) + { + getMessagePackageTableView().getSelectionModel().select(messagePackage); + getMessagePackageTableView().scrollTo(messagePackage); + return; + } + } + } + } + + /** + * Finds and selects the first row containing the text argument, after the currently selected row. + * @param text to search for. + */ + private void findNext(String text) + { + if(text != null && !text.isEmpty()) + { + MessagePackage selected = getMessagePackageTableView().getSelectionModel().getSelectedItem(); + + if(selected == null) + { + find(text); + return; + } + + int row = mFilteredMessagePackages.indexOf(selected); + + for(int x = row + 1; x < mFilteredMessagePackages.size(); x++) + { + if(x < mFilteredMessagePackages.size()) + { + MessagePackage messagePackage = mFilteredMessagePackages.get(x); + + if(messagePackage.toString().toLowerCase().contains(text.toLowerCase())) + { + getMessagePackageTableView().getSelectionModel().select(messagePackage); + getMessagePackageTableView().scrollTo(messagePackage); + return; + } + } + } + } + } + + private MessagePackageViewer getMessagePackageViewer() + { + if(mMessagePackageViewer == null) + { + mMessagePackageViewer = new MessagePackageViewer(); + mMessagePackageViewer.setMaxWidth(Double.MAX_VALUE); + + //Register for table selection events to display the selected value. + getMessagePackageTableView().getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> getMessagePackageViewer().set(newValue)); + } + + return mMessagePackageViewer; + } + + private IntegerTextField getWACNTextField() + { + if(mWACNTextField == null) + { + mWACNTextField = new IntegerTextField(); + } + + return mWACNTextField; + } + + private IntegerTextField getSystemTextField() + { + if(mSystemTextField == null) + { + mSystemTextField = new IntegerTextField(); + } + + return mSystemTextField; + } + + private IntegerTextField getNACTextField() + { + if(mNACTextField == null) + { + mNACTextField = new IntegerTextField(); + } + + return mNACTextField; + } + + private Button getReloadButton() + { + if(mReloadButton == null) + { + mReloadButton = new Button("Reload"); + mReloadButton.disableProperty().bind(Bindings.isNull(mLoadedFile)); + mReloadButton.setOnAction(event -> load(new File(mLoadedFile.get()))); + } + + return mReloadButton; + } + + /** + * List view control with message packages + */ + private TableView getMessagePackageTableView() + { + if(mMessagePackageTableView == null) + { + mMessagePackageTableView = new TableView<>(); + mMessagePackageTableView.setPlaceholder(getLoadingIndicator()); + SortedList sortedList = new SortedList<>(mFilteredMessagePackages); + sortedList.comparatorProperty().bind(mMessagePackageTableView.comparatorProperty()); + mMessagePackageTableView.setItems(sortedList); + + mMessagePackageTableView.setOnKeyPressed(event -> + { + if(KEY_CODE_COPY.match(event)) + { + final Set rows = new TreeSet<>(); + for (final TablePosition tablePosition : mMessagePackageTableView.getSelectionModel().getSelectedCells()) + { + rows.add(tablePosition.getRow()); + } + + final StringBuilder sb = new StringBuilder(); + boolean firstRow = true; + for (final Integer row : rows) + { + if(firstRow) + { + firstRow = false; + } + else + { + sb.append('\n'); + } + + boolean firstCol = true; + + for (final TableColumn column : mMessagePackageTableView.getColumns()) + { + if(firstCol) + { + firstCol = false; + } + else + { + sb.append('\t'); + } + + final Object cellData = column.getCellData(row); + sb.append(cellData == null ? "" : cellData.toString()); + } + } + final ClipboardContent clipboardContent = new ClipboardContent(); + clipboardContent.putString(sb.toString()); + Clipboard.getSystemClipboard().setContent(clipboardContent); + } + }); + + TableColumn timestampColumn = new TableColumn(); + timestampColumn.setPrefWidth(110); + timestampColumn.setText("Time"); + timestampColumn.setCellValueFactory(new PropertyValueFactory<>("timestamp")); + + TableColumn validColumn = new TableColumn(); + validColumn.setPrefWidth(50); + validColumn.setText("Valid"); + validColumn.setCellValueFactory(new PropertyValueFactory<>("valid")); + + TableColumn timeslotColumn = new TableColumn(); + timeslotColumn.setPrefWidth(35); + timeslotColumn.setText("TS"); + timeslotColumn.setCellValueFactory(new PropertyValueFactory<>("timeslot")); + + TableColumn messageColumn = new TableColumn(); + messageColumn.setPrefWidth(900); + messageColumn.setText("Message"); + messageColumn.setCellValueFactory((Callback) param -> { + SimpleStringProperty property = new SimpleStringProperty(); + if(param.getValue() instanceof MessagePackage messagePackage) + { + property.set(messagePackage.toString()); + } + + return property; + }); + + TableColumn decodeEventCountColumn = new TableColumn(); + decodeEventCountColumn.setPrefWidth(50); + decodeEventCountColumn.setText("Events"); + decodeEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decodeEventCount")); + + TableColumn decoderStateEventCountColumn = new TableColumn(); + decoderStateEventCountColumn.setPrefWidth(50); + decoderStateEventCountColumn.setText("States"); + decoderStateEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decoderStateEventCount")); + + TableColumn channelStartCountColumn = new TableColumn(); + channelStartCountColumn.setPrefWidth(50); + channelStartCountColumn.setText("Starts"); + channelStartCountColumn.setCellValueFactory(new PropertyValueFactory<>("channelStartProcessingRequestCount")); + + mMessagePackageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn, + decodeEventCountColumn, decoderStateEventCountColumn, channelStartCountColumn); + } + + return mMessagePackageTableView; + } + + /** + * File selection button + * @return button + */ + private Button getSelectFileButton() + { + if(mSelectFileButton == null) + { + mSelectFileButton = new Button("Select ..."); + mSelectFileButton.onActionProperty().set(event -> { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Select P25 Phase 2 .bits Recording"); + String lastDirectory = mPreferences.get(LAST_SELECTED_DIRECTORY, null); + if(lastDirectory != null) + { + File file = new File(lastDirectory); + if(file.exists() && file.isDirectory()) + { + fileChooser.setInitialDirectory(file); + } + } + fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("sdrtrunk bits recording", "*.bits")); + final File selected = fileChooser.showOpenDialog(getScene().getWindow()); + + if(selected != null) + { + mPreferences.put(LAST_SELECTED_DIRECTORY, selected.getParent()); + load(selected); + } + }); + } + + return mSelectFileButton; + } + + /** + * Spinny loading icon to show over the message table view + */ + private ProgressIndicator getLoadingIndicator() + { + if(mLoadingIndicator == null) + { + mLoadingIndicator = new ProgressIndicator(); + mLoadingIndicator.setProgress(-1); + mLoadingIndicator.setVisible(false); + } + + return mLoadingIndicator; + } + + /** + * Selected file path label. + */ + private Label getSelectedFileLabel() + { + if(mSelectedFileLabel == null) + { + mSelectedFileLabel = new Label(" "); + } + + return mSelectedFileLabel; + } + + /** + * Check box to apply filter to show/hide TS0 messages + * @return check box control + */ + private CheckBox getShowTS0() + { + if(mShowTS0 == null) + { + mShowTS0 = new CheckBox("TS0"); + mShowTS0.setSelected(true); + mShowTS0.setOnAction(event -> updateFilters()); + } + + return mShowTS0; + } + + /** + * Check box to apply filter to show/hide TS0 messages + * @return check box control + */ + private CheckBox getShowTS1() + { + if(mShowTS1 == null) + { + mShowTS1 = new CheckBox("TS1"); + mShowTS1.setSelected(true); + mShowTS1.setOnAction(event -> updateFilters()); + } + + return mShowTS1; + } + + /** + * Check box to apply filter to show/hide TS0 messages + * @return check box control + */ + private CheckBox getShowTS2() + { + if(mShowTS2 == null) + { + mShowTS2 = new CheckBox("TS2"); + mShowTS2.setSelected(true); + mShowTS2.setOnAction(event -> updateFilters()); + } + + return mShowTS2; + } + + /** + * Search text filter box + * @return text control for entering search text + */ + private TextField getSearchText() + { + if(mSearchText == null) + { + mSearchText = new TextField(); + mSearchText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters()); + } + + return mSearchText; + } + + /** + * Text box for find text + */ + private TextField getFindText() + { + if(mFindText == null) + { + mFindText = new TextField(); + mFindText.setOnKeyPressed(event -> { + if(event.getCode().equals(KeyCode.ENTER)) + { + getFindButton().fire(); + } + }); + mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters()); + } + + return mFindText; + } + + /** + * Find button to search for the text in the find text box. + * @return button + */ + private Button getFindButton() + { + if(mFindButton == null) + { + mFindButton = new Button("Find"); + mFindButton.setOnAction(event -> find(getFindText().getText())); + } + + return mFindButton; + } + + /** + * Find next button to search for the text in the find text box. + * @return button + */ + private Button getFindNextButton() + { + if(mFindNextButton == null) + { + mFindNextButton = new Button("Next"); + mFindNextButton.setOnAction(event -> findNext(getFindText().getText())); + } + + return mFindNextButton; + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/Form.java b/src/main/java/io/github/dsheirer/identifier/Form.java index dcc293927..8ad5ec688 100644 --- a/src/main/java/io/github/dsheirer/identifier/Form.java +++ b/src/main/java/io/github/dsheirer/identifier/Form.java @@ -18,6 +18,8 @@ */ package io.github.dsheirer.identifier; +import java.util.EnumSet; + /** * Identifier form. Indicates the type of identifier. */ @@ -64,4 +66,21 @@ public enum Form UNIQUE_ID, WACN, ANY; + + Form() + { + } + + /** + * Entity forms that are used to identify entities in P25 call events. + */ + public static EnumSet

ENTITY_FORMS = EnumSet.of(Form.RADIO, Form.TALKGROUP, Form.PATCH_GROUP, Form.TELEPHONE_NUMBER); + + /** + * Indicates if this form is a form that can be used to identify an entity + */ + public boolean isEntityForm() + { + return ENTITY_FORMS.contains(this); + } } diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierClass.java b/src/main/java/io/github/dsheirer/identifier/IdentifierClass.java index 51e54e497..4306fb305 100644 --- a/src/main/java/io/github/dsheirer/identifier/IdentifierClass.java +++ b/src/main/java/io/github/dsheirer/identifier/IdentifierClass.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -32,6 +32,12 @@ public enum IdentifierClass //Communication network identifier NETWORK, + //User network address + USER_NETWORK_ADDRESS, + + //User network port + USER_NETWORK_PORT, + //Communication user identifier USER; } diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java b/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java index bbe791119..12eaf1088 100644 --- a/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java +++ b/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -20,14 +20,13 @@ package io.github.dsheirer.identifier; import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * (Immutable) Collection of identifiers with convenient accessor methods @@ -274,43 +273,90 @@ public Identifier getIdentifier(IdentifierClass identifierClass, Form form, Role } /** - * Returns the first identifier in this collection that is assigned a FROM role + * Indicates if this collection has an identifier that matches the specified identiifer. + * @param toCheck identifier + * @return true if the identifier already exists in the collection. + */ + public boolean hasIdentifier(Identifier toCheck) + { + if(toCheck == null) + { + return false; + } + + for(Identifier identifier : mIdentifiers) + { + if(identifier.equals(toCheck)) + { + return true; + } + } + + return false; + } + + /** + * Returns the first entity identifier in this collection that is assigned a FROM role */ public Identifier getFromIdentifier() { Identifier from = getIdentifier(IdentifierClass.USER, Form.RADIO, Role.FROM); - if(from == null) + if(from != null) { - List fromIdentifiers = getIdentifiers(Role.FROM); + return from; + } - if(!fromIdentifiers.isEmpty()) - { - from = fromIdentifiers.get(0); - } + List identifiers = getIdentifiers(Form.TELEPHONE_NUMBER); + + if(!identifiers.isEmpty()) + { + return identifiers.get(0); + } + + List fromIdentifiers = getIdentifiers(Role.FROM); + + if(!fromIdentifiers.isEmpty()) + { + return fromIdentifiers.get(0); } - return from; + return null; } /** - * Returns the first identifier in this collection that is assigned a TO role + * Returns the first entity identifier in this collection that is assigned a TO role */ public Identifier getToIdentifier() { Identifier to = getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO); - if(to == null) + if(to != null) { - to = getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO); + return to; } - if(to == null) + to = getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO); + + if(to != null) { - List toIdentifiers = getIdentifiers(Role.TO); - if(!toIdentifiers.isEmpty()) + return to; + } + + to = getIdentifier(IdentifierClass.USER, Form.RADIO, Role.TO); + + if(to != null) + { + return to; + } + + List toIdentifiers = getIdentifiers(Role.TO); + + for(Identifier identifier: toIdentifiers) + { + if(identifier.getForm() != Form.ENCRYPTION_KEY) { - to = toIdentifiers.get(0); + return identifier; } } diff --git a/src/main/java/io/github/dsheirer/identifier/integer/IntegerIdentifier.java b/src/main/java/io/github/dsheirer/identifier/integer/IntegerIdentifier.java index 8351de0ea..01e0ba814 100644 --- a/src/main/java/io/github/dsheirer/identifier/integer/IntegerIdentifier.java +++ b/src/main/java/io/github/dsheirer/identifier/integer/IntegerIdentifier.java @@ -37,7 +37,6 @@ public IntegerIdentifier(int value, IdentifierClass identifierClass, Form form, super(value, identifierClass, form, role); } - @Override public boolean equals(Object o) { diff --git a/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Identifier.java b/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Identifier.java index 94adcbd8f..defe35a82 100644 --- a/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Identifier.java +++ b/src/main/java/io/github/dsheirer/identifier/ipv4/IPV4Identifier.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * ***************************************************************************** + * **************************************************************************** */ package io.github.dsheirer.identifier.ipv4; @@ -29,6 +28,6 @@ public abstract class IPV4Identifier extends Identifier { public IPV4Identifier(IPV4Address value, Role role) { - super(value, IdentifierClass.USER, Form.IPV4_ADDRESS, role); + super(value, IdentifierClass.USER_NETWORK_ADDRESS, Form.IPV4_ADDRESS, role); } } diff --git a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroup.java b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroup.java index 80e53b5b7..eabd7cb52 100644 --- a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroup.java +++ b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroup.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -95,25 +95,44 @@ public boolean hasPatchedRadios() /** * Adds the talkgroup identifier to this patched group. * @param patchedGroupIdentifier to add + * @return true if the patched group identifier is new and was added */ - public void addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier) + public boolean addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier) { + boolean added = false; + if(!mPatchedTalkgroupIdentifiers.contains(patchedGroupIdentifier)) { - mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier); + added |= mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier); } + + return added; + } + + /** + * Removes the patched talkgroup from this patch group. + * @param talkgroup to remove + */ + public void removePatchedTalkgroup(TalkgroupIdentifier talkgroup) + { + mPatchedTalkgroupIdentifiers.remove(talkgroup); } + /** * Adds a list of talkgroup identifiers to this patched group. * @param patchedGroupIdentifiers to add */ - public void addPatchedTalkgroups(List patchedGroupIdentifiers) + public boolean addPatchedTalkgroups(List patchedGroupIdentifiers) { + boolean added = false; + for(TalkgroupIdentifier identifier : patchedGroupIdentifiers) { - addPatchedTalkgroup(identifier); + added |= addPatchedTalkgroup(identifier); } + + return added; } /** @@ -129,24 +148,41 @@ public List getPatchedTalkgroupIdentifiers() * Adds the radio identifier to this patched group. * @param patchedRadioIdentifier to add */ - public void addPatchedRadio(RadioIdentifier patchedRadioIdentifier) + public boolean addPatchedRadio(RadioIdentifier patchedRadioIdentifier) { if(!mPatchedRadioIdentifiers.contains(patchedRadioIdentifier)) { mPatchedRadioIdentifiers.add(patchedRadioIdentifier); + return true; } + + return false; + } + + /** + * Removes the patched radio from this patch group. + * @param radioIdentifier to remove + */ + public void removePatchedRadio(RadioIdentifier radioIdentifier) + { + mPatchedRadioIdentifiers.remove(radioIdentifier); } /** * Adds a list of radio identifiers to this patched group. * @param patchedRadioIdentifiers to add + * @return true if any new identifiers were added */ - public void addPatchedRadios(List patchedRadioIdentifiers) + public boolean addPatchedRadios(List patchedRadioIdentifiers) { + boolean added = false; + for(RadioIdentifier identifier : patchedRadioIdentifiers) { - addPatchedRadio(identifier); + added |= addPatchedRadio(identifier); } + + return added; } /** diff --git a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java index 4b135b555..959f30c88 100644 --- a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java +++ b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -22,11 +22,14 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.IdentifierClass; import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * Manager for (temporary) patch groups aka super groups. This manager monitors patch group additions and deletions and @@ -39,7 +42,8 @@ */ public class PatchGroupManager { - private Map mPatchGroupMap = new HashMap<>(); + private static final long PATCH_GROUP_FRESHNESS_THRESHOLD_MS = Duration.ofSeconds(30).toMillis(); + private Map mPatchGroupTrackerMap = new HashMap<>(); /** * Constructs an instance @@ -48,12 +52,36 @@ public PatchGroupManager() { } + /** + * Summary listing of active patch groups + */ + public String getPatchGroupSummary() + { + StringBuilder sb = new StringBuilder(); + sb.append("Active Patch Groups\n"); + List trackers = new ArrayList<>(mPatchGroupTrackerMap.values()); + + if(trackers.isEmpty()) + { + sb.append("None"); + } + else + { + for(PatchGroupTracker tracker : trackers) + { + sb.append(" ").append(tracker.mPatchGroupIdentifier).append("\n"); + } + } + + return sb.toString(); + } + /** * Clears any existing patch groups */ public void clear() { - mPatchGroupMap.clear(); + mPatchGroupTrackerMap.clear(); } /** @@ -63,46 +91,53 @@ public void clear() * existing patch group is discarded and replaced with the updated version of the patch group. * * @param patchGroupIdentifier to add or update + * @return true if the group was added or updated */ - public synchronized void addPatchGroup(PatchGroupIdentifier patchGroupIdentifier) + public synchronized boolean addPatchGroup(PatchGroupIdentifier patchGroupIdentifier, long timestamp) { PatchGroup update = patchGroupIdentifier.getValue(); - if(update.getPatchGroup().getValue() > 0) + int patchGroup = update.getPatchGroup().getValue(); + + if(patchGroup > 0) { - if(mPatchGroupMap.containsKey(update.getPatchGroup().getValue())) + if(mPatchGroupTrackerMap.containsKey(patchGroup)) { - PatchGroup existingPatchGroup = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue(); + PatchGroupTracker tracker = mPatchGroupTrackerMap.get(patchGroup); - //If the patch group version number is the same, this is an update - if(existingPatchGroup.getVersion() == update.getVersion()) + if(tracker.isStale(timestamp)) { - existingPatchGroup.addPatchedTalkgroups(update.getPatchedTalkgroupIdentifiers()); - existingPatchGroup.addPatchedRadios(update.getPatchedRadioIdentifiers()); + //Replace the existing patch group if it is stale. + mPatchGroupTrackerMap.put(patchGroup, new PatchGroupTracker(patchGroupIdentifier, timestamp)); + return true; } - //Otherwise, this is a replace operation. else { - mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier); + //Update the existing patch group. + return tracker.add(patchGroupIdentifier, timestamp); } } else { - mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier); + mPatchGroupTrackerMap.put(patchGroup, new PatchGroupTracker(patchGroupIdentifier, timestamp)); + return true; } } + + return false; } /** * Adds any patch group identifiers contained in the list. + * @param referenceTimestamp as a reference for checking staleness of existing patch groups. */ - public synchronized void addPatchGroups(List identifiers) + public synchronized void addPatchGroups(List identifiers, long referenceTimestamp) { for(Identifier identifier : identifiers) { if(identifier instanceof PatchGroupIdentifier) { - addPatchGroup((PatchGroupIdentifier)identifier); + addPatchGroup((PatchGroupIdentifier)identifier, referenceTimestamp); } } } @@ -111,11 +146,12 @@ public synchronized void addPatchGroups(List identifiers) * Removes the patch group from this manager if it is currently being managed. * * @param patchGroupIdentifier to remove + * @return true if the patch group was removed. */ - public synchronized void removePatchGroup(PatchGroupIdentifier patchGroupIdentifier) + public synchronized boolean removePatchGroup(PatchGroupIdentifier patchGroupIdentifier) { int id = patchGroupIdentifier.getValue().getPatchGroup().getValue(); - mPatchGroupMap.remove(id); + return mPatchGroupTrackerMap.remove(id) != null; } /** @@ -136,15 +172,16 @@ public synchronized void removePatchGroups(List identifiers) * Updates the list of identifiers by replacing any talkgroups or patch groups with the current * version of the patch group and a complete listing of the patched talkgroups. * @param identifiers to update + * @param referenceTimestamp as a reference for checking staleness of existing patch groups. * @return list of identifiers */ - public List update(List identifiers) + public List update(List identifiers, long referenceTimestamp) { List updated = new ArrayList<>(); for(Identifier identifier: identifiers) { - updated.add(update(identifier)); + updated.add(update(identifier, referenceTimestamp)); } return updated; @@ -155,9 +192,10 @@ public List update(List identifiers) * if the identifier matches a currently managed patch group. * * @param identifier for a talkgroup or a patch group. + * @param referenceTimestamp as a reference for checking staleness of existing patch groups. * @return current patch group or the original identifier */ - public Identifier update(Identifier identifier) + public Identifier update(Identifier identifier, long referenceTimestamp) { if(identifier != null && identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO) { @@ -166,22 +204,49 @@ public Identifier update(Identifier identifier) case TALKGROUP: if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier) { - Identifier mapValue = mPatchGroupMap.get(talkgroupIdentifier.getValue()); - if (mapValue != null) + int id = talkgroupIdentifier.getValue(); + + PatchGroupTracker tracker = mPatchGroupTrackerMap.get(id); + + if(tracker != null) { - return mapValue; + if(tracker.isStale(referenceTimestamp)) + { + mPatchGroupTrackerMap.remove(id); + } + else + { + //Perform substitution - return patch group instead of the original talkgroup + return tracker.getPatchGroupIdentifier(referenceTimestamp); + } } } break; case PATCH_GROUP: if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier) { - int patchGroupId = patchGroupIdentifier.getValue().getPatchGroup().getValue(); + int id = patchGroupIdentifier.getValue().getPatchGroup().getValue(); + + PatchGroupTracker tracker = mPatchGroupTrackerMap.get(id); - Identifier mapValue = mPatchGroupMap.get(patchGroupId); - if (mapValue != null) + if(tracker != null) { - return mapValue; + if(tracker.isStale(referenceTimestamp)) + { + mPatchGroupTrackerMap.put(id, new PatchGroupTracker(patchGroupIdentifier, referenceTimestamp)); + mPatchGroupTrackerMap.remove(id); + } + else if(tracker.getPatchGroupIdentifier(referenceTimestamp).getValue().getVersion() != + patchGroupIdentifier.getValue().getVersion()) + { + mPatchGroupTrackerMap.put(id, new PatchGroupTracker(patchGroupIdentifier, referenceTimestamp)); + mPatchGroupTrackerMap.remove(id); + } + else + { + //Perform substitution - return patch group instead of the original talkgroup + return tracker.getPatchGroupIdentifier(referenceTimestamp); + } } } break; @@ -190,4 +255,107 @@ public Identifier update(Identifier identifier) return identifier; } + + /** + * Tracks the freshness of a patch group and any member talkgroup and radio identifiers to ensure that stale + * patch group identifiers are removed. + */ + public class PatchGroupTracker + { + private PatchGroupIdentifier mPatchGroupIdentifier; + private long mLastUpdateTimestamp; + private Map mTalkgroupTimestampMap = new HashMap<>(); + private Map mRadioTimestampMap = new HashMap<>(); + + /** + * Constructs an instance + * @param patchGroupIdentifier + */ + public PatchGroupTracker(PatchGroupIdentifier patchGroupIdentifier, long timestamp) + { + mPatchGroupIdentifier = patchGroupIdentifier; + add(patchGroupIdentifier, timestamp); + } + + /** + * Patch group monitored by this tracker. This method will cleanup and remove any stale radio or talkgroup + * identifiers on access. + * + * Note: this tracker should be checked for freshness by first checking the isStale() method before accessing + * the tracked patch group by this method. + * + * @param referenceTimestamp for comparing stored radio and talkgroup values for staleness. + * @return current version of the talkgroup + */ + public PatchGroupIdentifier getPatchGroupIdentifier(long referenceTimestamp) + { + //Remove stale talkgroups + List> toRemove = mTalkgroupTimestampMap.entrySet().stream() + .filter(entry -> isStale(entry.getValue(), referenceTimestamp)).collect(Collectors.toList()); + + for(Map.Entry entry: toRemove) + { + mTalkgroupTimestampMap.remove(entry.getKey()); + mPatchGroupIdentifier.getValue().removePatchedTalkgroup(entry.getKey()); + } + + //Remove stale radios + List> toRemove2 = mRadioTimestampMap.entrySet().stream() + .filter(entry -> isStale(entry.getValue(), referenceTimestamp)).collect(Collectors.toList()); + + for(Map.Entry entry: toRemove2) + { + mRadioTimestampMap.remove(entry.getKey()); + mPatchGroupIdentifier.getValue().removePatchedRadio(entry.getKey()); + } + + return mPatchGroupIdentifier; + } + + /** + * Indicates if the stored timestamp is stale relative to the reference timestamp. + * @param storedTimestamp to compare + * @param referenceTimestamp representing the current time. + * @return true of the stored timestamp is stale. + */ + private boolean isStale(long storedTimestamp, long referenceTimestamp) + { + return referenceTimestamp - storedTimestamp > PATCH_GROUP_FRESHNESS_THRESHOLD_MS; + } + + /** + * Indicates if the tracked patch group is stale and hasn't received any add/updates within a period of time. + * @param referenceTimestamp representing now for comparison against the patch groups update timestamp. + * @return true if the tracked patch group is stale. + */ + public boolean isStale(long referenceTimestamp) + { + return isStale(mLastUpdateTimestamp, referenceTimestamp); + } + + /** + * Add or update the patch group and update the freshness timestamp. + * @param patchGroupIdentifier containing an add value. + */ + public boolean add(PatchGroupIdentifier patchGroupIdentifier, long timestamp) + { + mLastUpdateTimestamp = timestamp; + + boolean added = false; + + for(TalkgroupIdentifier talkgroup: patchGroupIdentifier.getValue().getPatchedTalkgroupIdentifiers()) + { + mTalkgroupTimestampMap.put(talkgroup, timestamp); + added |= mPatchGroupIdentifier.getValue().addPatchedTalkgroup(talkgroup); + } + + for(RadioIdentifier radio: patchGroupIdentifier.getValue().getPatchedRadioIdentifiers()) + { + mRadioTimestampMap.put(radio, timestamp); + added |= mPatchGroupIdentifier.getValue().addPatchedRadio(radio); + } + + return added; + } + } } diff --git a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupPreLoadDataContent.java b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupPreLoadDataContent.java index 2172feced..3d2e4a90f 100644 --- a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupPreLoadDataContent.java +++ b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupPreLoadDataContent.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -27,13 +27,25 @@ */ public class PatchGroupPreLoadDataContent extends PreloadDataContent { + private long mTimestamp; + /** * Constructs an instance * * @param data to preload */ - public PatchGroupPreLoadDataContent(IdentifierCollection data) + public PatchGroupPreLoadDataContent(IdentifierCollection data, long timestamp) { super(data); + mTimestamp = timestamp; + } + + /** + * Freshness timestamp for the preload data. + * @return timestamp milliseconds. + */ + public long getTimestamp() + { + return mTimestamp; } } diff --git a/src/main/java/io/github/dsheirer/identifier/radio/FullyQualifiedRadioIdentifier.java b/src/main/java/io/github/dsheirer/identifier/radio/FullyQualifiedRadioIdentifier.java index bdc2b9977..0d1d3547c 100644 --- a/src/main/java/io/github/dsheirer/identifier/radio/FullyQualifiedRadioIdentifier.java +++ b/src/main/java/io/github/dsheirer/identifier/radio/FullyQualifiedRadioIdentifier.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.identifier.radio; @@ -25,29 +22,74 @@ import io.github.dsheirer.identifier.Role; /** - * Fully qualified radio identifier + * Fully qualified radio identifier. This is used for roaming radios or any time there is a need to fully qualify a + * radio. */ public abstract class FullyQualifiedRadioIdentifier extends RadioIdentifier { private int mWacn; private int mSystem; + private int mRadio; /** * Constructs an instance - * @param wacn - * @param system - * @param id + * @param localAddress radio identifier, aka alias. This can be the same as the radio ID when the fully qualified radio + * is not being aliased on a local radio system. + * @param wacn of the home network for the radio. + * @param system of the home network for the radio. + * @param id of the radio within the home network. */ - public FullyQualifiedRadioIdentifier(int wacn, int system, int id, Role role) + public FullyQualifiedRadioIdentifier(int localAddress, int wacn, int system, int id, Role role) { - super(id, role); + super(localAddress > 0 ? localAddress : id, role); mWacn = wacn; mSystem = system; + mRadio = id; + } + + public int getWacn() + { + return mWacn; + } + + public int getSystem() + { + return mSystem; + } + + public int getRadio() + { + return mRadio; + } + + /** + * Fully qualified radio identity. + * @return radio identity + */ + public String getFullyQualifiedRadioAddress() + { + return mWacn + "." + mSystem + "." + mRadio; + } + + /** + * Indicates if the radio identify is aliased with a persona value that is different from the radio ID. + * @return true if aliased. + */ + public boolean isAliased() + { + return getValue() != mRadio; } @Override public String toString() { - return mWacn + "." + mSystem + "." + super.toString(); + if(isAliased()) + { + return super.toString() + "(" + getFullyQualifiedRadioAddress() + ")"; + } + else + { + return getFullyQualifiedRadioAddress(); + } } } diff --git a/src/main/java/io/github/dsheirer/identifier/radio/RadioIdentifier.java b/src/main/java/io/github/dsheirer/identifier/radio/RadioIdentifier.java index 077e10d88..490fa7e08 100644 --- a/src/main/java/io/github/dsheirer/identifier/radio/RadioIdentifier.java +++ b/src/main/java/io/github/dsheirer/identifier/radio/RadioIdentifier.java @@ -26,7 +26,14 @@ import io.github.dsheirer.identifier.IdentifierClass; import io.github.dsheirer.identifier.Role; import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +/** + * Radio identifier. + * + * Note: this class overrides the .equals method to ensure that all radios and subclasses can be compared using + * the radio value, regardless if it is a simple radio or a fully qualified radio identifier. + */ public abstract class RadioIdentifier extends IntegerIdentifier { public RadioIdentifier(Integer value, Role role) @@ -39,4 +46,28 @@ public boolean isValid() { return getValue() > 0; } + + /** + * Overrides to compare just the radio value, class, form, role and protocol. This allows a fully qualified + * radio identifier to be equivalent to a standard radio identifier for the traffic channel manager. + */ + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + + if(o instanceof RadioIdentifier radio) + { + return (getValue().intValue() == radio.getValue().intValue()) && + getIdentifierClass() == radio.getIdentifierClass() && + getForm() == radio.getForm() && + getRole() == radio.getRole() && + getProtocol() == radio.getProtocol(); + } + + return false; + } } diff --git a/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedTalkgroupIdentifier.java b/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedTalkgroupIdentifier.java index 1e051f6ad..fe5a69a31 100644 --- a/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedTalkgroupIdentifier.java +++ b/src/main/java/io/github/dsheirer/identifier/talkgroup/FullyQualifiedTalkgroupIdentifier.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.identifier.talkgroup; @@ -31,23 +28,67 @@ public abstract class FullyQualifiedTalkgroupIdentifier extends TalkgroupIdentif { private int mWacn; private int mSystem; + private int mTalkgroup; /** * Constructs an instance - * @param wacn - * @param system - * @param id + * @param localAddress used on the local system as an alias to the fully qualified talkgroup. + * @param wacn for the talkgroup home system. + * @param system for the talkgroup home system. + * @param id for the talkgroup within the home system. + * @param role played by the talkgroup. */ - public FullyQualifiedTalkgroupIdentifier(int wacn, int system, int id, Role role) + public FullyQualifiedTalkgroupIdentifier(int localAddress, int wacn, int system, int id, Role role) { - super(id, role); + super(localAddress > 0 ? localAddress : id, role); mWacn = wacn; mSystem = system; + mTalkgroup = id; + } + + public int getWacn() + { + return mWacn; + } + + public int getSystem() + { + return mSystem; + } + + public int getTalkgroup() + { + return mTalkgroup; + } + + /** + * Fully qualified talkgroup identity. + * @return talkgroup identity + */ + public String getFullyQualifiedTalkgroupAddress() + { + return mWacn + "." + mSystem + "." + mTalkgroup; + } + + /** + * Indicates if the talkgroup identify is aliased with a persona value that is different from the talkgroup ID. + * @return true if aliased. + */ + public boolean isAliased() + { + return getValue() != mTalkgroup; } @Override public String toString() { - return mWacn + "." + mSystem + "." + super.toString(); + if(isAliased()) + { + return super.toString() + "(" + getFullyQualifiedTalkgroupAddress() + ")"; + } + else + { + return getFullyQualifiedTalkgroupAddress(); + } } } diff --git a/src/main/java/io/github/dsheirer/identifier/talkgroup/TalkgroupIdentifier.java b/src/main/java/io/github/dsheirer/identifier/talkgroup/TalkgroupIdentifier.java index 3e7bdd61f..852038ebd 100644 --- a/src/main/java/io/github/dsheirer/identifier/talkgroup/TalkgroupIdentifier.java +++ b/src/main/java/io/github/dsheirer/identifier/talkgroup/TalkgroupIdentifier.java @@ -27,8 +27,19 @@ import io.github.dsheirer.identifier.Role; import io.github.dsheirer.identifier.integer.IntegerIdentifier; +/** + * Talkgroup identifier. + * + * Note: this class overrides the .equals method to ensure that all talkgroups and subclasses can be compared using + * the talkgroup value, regardless if it is a simple talkgroup or a fully qualified talkgroup identifier. + */ public abstract class TalkgroupIdentifier extends IntegerIdentifier { + /** + * Constructs an instance + * @param value for the talkgroup + * @param role for the talkgroup + */ public TalkgroupIdentifier(Integer value, Role role) { super(value, IdentifierClass.USER, Form.TALKGROUP, role); @@ -39,4 +50,28 @@ public boolean isValid() { return getValue() > 0; } + + /** + * Overrides to compare just the talkgroup value, class, form, role and protocol. This allows a fully qualified + * talkgroup identifier to be equivalent to a standard talkgroup identifier for the traffic channel manager. + */ + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + + if(o instanceof TalkgroupIdentifier tg) + { + return (getValue().intValue() == tg.getValue().intValue()) && + getIdentifierClass() == tg.getIdentifierClass() && + getForm() == tg.getForm() && + getRole() == tg.getRole() && + getProtocol() == tg.getProtocol(); + } + + return false; + } } diff --git a/src/main/java/io/github/dsheirer/message/AbstractMessage.java b/src/main/java/io/github/dsheirer/message/AbstractMessage.java new file mode 100644 index 000000000..5689b4b57 --- /dev/null +++ b/src/main/java/io/github/dsheirer/message/AbstractMessage.java @@ -0,0 +1,191 @@ +/* + * ***************************************************************************** + * 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.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.FragmentedIntField; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.bits.LongField; + +/** + * Base message class with general utility methods for accessing fields in BinaryMessages. + */ +public abstract class AbstractMessage +{ + private CorrectedBinaryMessage mMessage; + private int mOffset = 0; + + /** + * Constructs an instance + * @param message containing the message bits. + * @param offset to the start of the message within the corrected binary message. + */ + public AbstractMessage(CorrectedBinaryMessage message, int offset) + { + mMessage = message; + mOffset = offset; + } + + /** + * Constructs an instance. + * @param message bits + */ + public AbstractMessage(CorrectedBinaryMessage message) + { + this(message, 0); + } + + /** + * Indicates if this message is offset within the corrected binary message. + * @return true of offset is non-zero. + */ + private boolean hasOffset() + { + return mOffset > 0; + } + + /** + * Offset to the start of the message (fragement) within the corrected binary message. + */ + public int getOffset() + { + return mOffset; + } + + /** + * Binary (bits) message for this message. + * @return corrected binary message + */ + public CorrectedBinaryMessage getMessage() + { + return mMessage; + } + + /** + * Indicates if the field has a non-zero value, meaning that any of the bits for the field are set to a one. + * @param field to inspect. + * @return true if the field has a non-zero value. + */ + public boolean hasInt(IntField field) + { + if(hasOffset()) + { + return getMessage().hasInt(field, getOffset()); + } + else + { + return getMessage().hasInt(field); + } + } + + /** + * Gets the integer field value and formats it as hexadecimal number + * @param field to parse + * @param places to format (ie 2 for a byte, 4 for 2-bytes, etc.) + * @return formatted hex value. + */ + public String getIntAsHex(IntField field, int places) + { + int value = getInt(field); + String hex = Integer.toHexString(value).toUpperCase(); + while(hex.length() < places) + { + hex = "0" + hex; + } + + return hex; + } + + /** + * Access the integer value for the specified field. + * @param field to access + * @return integer value for the field. + */ + public int getInt(IntField field) + { + if(hasOffset()) + { + return getMessage().getInt(field, getOffset()); + } + else + { + return getMessage().getInt(field); + } + } + + /** + * Indicates if the field has a non-zero value, meaning that any of the bits for the field are set to a one. + * @param field to inspect. + * @return true if the field has a non-zero value. + */ + public boolean hasInt(FragmentedIntField field) + { + if(hasOffset()) + { + return getMessage().hasInt(field, getOffset()); + } + else + { + return getMessage().hasInt(field); + } + } + + /** + * Access the integer value for the specified field. + * @param field to access + * @return integer value for the field. + */ + public int getInt(FragmentedIntField field) + { + if(hasOffset()) + { + return getMessage().getInt(field, getOffset()); + } + else + { + return getMessage().getInt(field); + } + } + + /** + * Access the long value for the specified field. + * @param field to access + * @return value for the field. + */ + public long getLong(LongField field) + { + if(hasOffset()) + { + return getMessage().getLong(field, getOffset()); + } + else + { + return getMessage().getLong(field); + } + } + + /** + * Utility method to format an octet as hex. + */ + public static String formatOctetAsHex(int value) + { + return String.format("%02X", value); + } +} diff --git a/src/main/java/io/github/dsheirer/message/TimeslotMessage.java b/src/main/java/io/github/dsheirer/message/TimeslotMessage.java new file mode 100644 index 000000000..8da4091dd --- /dev/null +++ b/src/main/java/io/github/dsheirer/message/TimeslotMessage.java @@ -0,0 +1,103 @@ +/* + * ***************************************************************************** + * 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.message; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; + +/** + * Timeslot message base class. + */ +public abstract class TimeslotMessage extends AbstractMessage +{ + public static final int TIMESLOT_0 = 0; + public static final int TIMESLOT_1 = 1; + public static final int TIMESLOT_2 = 2; + private long mTimestamp; + private int mTimeslot; + private boolean mValid = true; + + /** + * Constructs an instance + * @param message containing binary fields that may contain fields outside of the scope of this class and thus + * requires an offset value that points to the start of this message structure within the larger message context. + * @param offset in the binary message to the start of this chunk of the message. + * @param timeslot for this message + * @param timestamp for this message + */ + public TimeslotMessage(CorrectedBinaryMessage message, int offset, int timeslot, long timestamp) + { + super(message, offset); + mTimeslot = timeslot; + mTimestamp = timestamp; + } + + /** + * Constructs an instance + * @param message containing binary fields. + * @param timeslot for this message + * @param timestamp for this message + */ + public TimeslotMessage(CorrectedBinaryMessage message, int timeslot, long timestamp) + { + super(message); + mTimeslot = timeslot; + mTimestamp = timestamp; + } + + /** + * Timeslot for this message. + * @return timeslot, default of 0 for no timeslot. + */ + public int getTimeslot() + { + return mTimeslot; + } + + /** + * Timestamp when the message was received or processed. This timestamp should be as close to + * accurate as possible. + */ + public long getTimestamp() + { + return mTimestamp; + } + + /** + * Decoded textual representation of the message + */ + public abstract String toString(); + + /** + * Indicates if the message is valid and has passed crc/integrity checks + */ + public boolean isValid() + { + return mValid; + } + + /** + * Sets the validity flag to indicate if the message is valid and passed any CRC or integrity checks. + * @param valid flag + */ + public void setValid(boolean valid) + { + mValid = valid; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java index 3b7c392b1..cb766ba1f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java @@ -32,6 +32,7 @@ import io.github.dsheirer.filter.AllPassFilter; import io.github.dsheirer.filter.FilterSet; import io.github.dsheirer.filter.IFilter; +import io.github.dsheirer.identifier.patch.PatchGroupManager; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.module.Module; @@ -51,6 +52,7 @@ import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn; +import io.github.dsheirer.module.decode.dmr.message.DMRMessage; import io.github.dsheirer.module.decode.dmr.message.filter.DmrMessageFilterSet; import io.github.dsheirer.module.decode.event.DecodeEvent; import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder; @@ -90,6 +92,7 @@ import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2; import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSK; import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState; +import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; import io.github.dsheirer.module.decode.p25.phase2.message.filter.P25P2MessageFilterSet; import io.github.dsheirer.module.decode.passport.DecodeConfigPassport; import io.github.dsheirer.module.decode.passport.PassportDecoder; @@ -149,7 +152,7 @@ public static List getPrimaryModules(ChannelMapModel channelMapModel, Ch UserPreferences userPreferences, TrafficChannelManager trafficChannelManager, IChannelDescriptor channelDescriptor) { - List modules = new ArrayList(); + List modules = new ArrayList<>(); AliasList aliasList = aliasModel.getAliasList(channel.getAliasListName()); modules.add(new AliasActionManager(aliasList)); @@ -178,7 +181,8 @@ public static List getPrimaryModules(ChannelMapModel channelMapModel, Ch processLTRNet(channel, modules, aliasList, (DecodeConfigLTRNet) decodeConfig); break; case MPT1327: - processMPT1327(channelMapModel, channel, modules, aliasList, channelType, (DecodeConfigMPT1327) decodeConfig); + processMPT1327(channelMapModel, channel, modules, aliasList, channelType, + (DecodeConfigMPT1327) decodeConfig, userPreferences); break; case PASSPORT: processPassport(channel, modules, aliasList, decodeConfig); @@ -187,7 +191,7 @@ public static List getPrimaryModules(ChannelMapModel channelMapModel, Ch processP25Phase1(channel, userPreferences, modules, aliasList, channelType, (DecodeConfigP25Phase1) decodeConfig); break; case P25_PHASE2: - processP25Phase2(channel, userPreferences, modules, aliasList); + processP25Phase2(channel, userPreferences, modules, aliasList, trafficChannelManager); break; default: throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]"); @@ -202,14 +206,53 @@ public static List getPrimaryModules(ChannelMapModel channelMapModel, Ch * @param userPreferences reference * @param modules collection to add to * @param aliasList for the channel + * @param trafficChannelManager from parent, if this is for a traffic channel, otherwise this can be null and it + * will be created automatically. */ - private static void processP25Phase2(Channel channel, UserPreferences userPreferences, List modules, AliasList aliasList) { + private static void processP25Phase2(Channel channel, UserPreferences userPreferences, List modules, + AliasList aliasList, TrafficChannelManager trafficChannelManager) + { + modules.add(new P25P2DecoderHDQPSK((DecodeConfigP25Phase2)channel.getDecodeConfiguration())); - modules.add(new P25P2DecoderState(channel, 0)); - modules.add(new P25P2DecoderState(channel, 1)); - modules.add(new P25P2AudioModule(userPreferences, 0, aliasList)); - modules.add(new P25P2AudioModule(userPreferences, 1, aliasList)); + P25TrafficChannelManager p25TrafficChannelManager = null; + + if(channel.getChannelType() == ChannelType.STANDARD) + { + p25TrafficChannelManager = new P25TrafficChannelManager(channel); + } + else if(trafficChannelManager instanceof P25TrafficChannelManager p25) + { + p25TrafficChannelManager = p25; + } + else + { + p25TrafficChannelManager = new P25TrafficChannelManager(channel); + } + + //Only add traffic channel manager to the modules if this is the control channel + if(channel.isStandardChannel()) + { + modules.add(p25TrafficChannelManager); + } + + PatchGroupManager patchGroupManager = new PatchGroupManager(); + modules.add(new P25P2DecoderState(channel, P25P2Message.TIMESLOT_1, p25TrafficChannelManager, patchGroupManager)); + modules.add(new P25P2DecoderState(channel, P25P2Message.TIMESLOT_2, p25TrafficChannelManager, patchGroupManager)); + modules.add(new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_1, aliasList)); + modules.add(new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_2, aliasList)); + + + //Add a channel rotation monitor when we have multiple control channel frequencies specified + if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency && + ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies()) + { + List activeStates = new ArrayList<>(); + activeStates.add(State.CONTROL); + modules.add(new ChannelRotationMonitor(activeStates, + ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay(), + userPreferences)); + } } /** @@ -219,7 +262,8 @@ private static void processP25Phase2(Channel channel, UserPreferences userPrefer * @param modules collection to add to * @param aliasList for the channel */ - private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List modules, AliasList aliasList, ChannelType channelType, DecodeConfigP25Phase1 decodeConfig) { + private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List modules, AliasList aliasList, ChannelType channelType, DecodeConfigP25Phase1 decodeConfig) + { DecodeConfigP25Phase1 p25Config = decodeConfig; switch(p25Config.getModulation()) @@ -249,13 +293,12 @@ private static void processP25Phase1(Channel channel, UserPreferences userPrefer modules.add(new P25P1AudioModule(userPreferences, aliasList)); //Add a channel rotation monitor when we have multiple control channel frequencies specified - if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency && - ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies()) + if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf && + sctmf.hasMultipleFrequencies()) { List activeStates = new ArrayList<>(); activeStates.add(State.CONTROL); - modules.add(new ChannelRotationMonitor(activeStates, - ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay())); + modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences)); } } @@ -285,7 +328,10 @@ private static void processPassport(Channel channel, List modules, Alias * @param channelType for control or traffic * @param decodeConfig configuration */ - private static void processMPT1327(ChannelMapModel channelMapModel, Channel channel, List modules, AliasList aliasList, ChannelType channelType, DecodeConfigMPT1327 decodeConfig) { + private static void processMPT1327(ChannelMapModel channelMapModel, Channel channel, List modules, + AliasList aliasList, ChannelType channelType, DecodeConfigMPT1327 decodeConfig, + UserPreferences userPreferences) + { DecodeConfigMPT1327 mptConfig = decodeConfig; ChannelMap channelMap = channelMapModel.getChannelMap(mptConfig.getChannelMapName()); Sync sync = mptConfig.getSync(); @@ -317,13 +363,12 @@ private static void processMPT1327(ChannelMapModel channelMapModel, Channel chan } //Add a channel rotation monitor when we have multiple control channel frequencies specified - if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency && - ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies()) + if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf && + sctmf.hasMultipleFrequencies()) { List activeStates = new ArrayList<>(); activeStates.add(State.CONTROL); - modules.add(new ChannelRotationMonitor(activeStates, - ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay())); + modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences)); } } @@ -440,8 +485,8 @@ private static void processDMR(Channel channel, UserPreferences userPreferences, modules.add(dmrTrafficChannelManager); } - DMRDecoderState state1 = new DMRDecoderState(channel, 1, dmrTrafficChannelManager); - DMRDecoderState state2 = new DMRDecoderState(channel, 2, dmrTrafficChannelManager); + DMRDecoderState state1 = new DMRDecoderState(channel, DMRMessage.TIMESLOT_1, dmrTrafficChannelManager); + DMRDecoderState state2 = new DMRDecoderState(channel, DMRMessage.TIMESLOT_2, dmrTrafficChannelManager); //Register the states with each other so that they can pass Cap+ site status messaging to resolve current channel state1.setSisterDecoderState(state2); @@ -457,7 +502,7 @@ private static void processDMR(Channel channel, UserPreferences userPreferences, lsn.setTimeslotFrequency(rest.getTimeslotFrequency()); } - if(lsn.getTimeslot() == 1) + if(lsn.getTimeslot() == DMRMessage.TIMESLOT_1) { state1.setCurrentChannel(lsn); state2.setCurrentChannel(lsn.getSisterTimeslot()); @@ -473,7 +518,7 @@ private static void processDMR(Channel channel, UserPreferences userPreferences, { DecodeEvent event = decodeConfig.getChannelGrantEvent(); - if(decodeConfig.getChannelGrantEvent().getTimeslot() == 1) + if(decodeConfig.getChannelGrantEvent().getTimeslot() == DMRMessage.TIMESLOT_1) { state1.setCurrentCallEvent(event); @@ -495,17 +540,16 @@ private static void processDMR(Channel channel, UserPreferences userPreferences, modules.add(state1); modules.add(state2); - modules.add(new DMRAudioModule(userPreferences, aliasList, 1)); - modules.add(new DMRAudioModule(userPreferences, aliasList, 2)); + modules.add(new DMRAudioModule(userPreferences, aliasList, DMRMessage.TIMESLOT_1)); + modules.add(new DMRAudioModule(userPreferences, aliasList, DMRMessage.TIMESLOT_2)); //Add a channel rotation monitor when we have multiple control channel frequencies specified - if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency && - ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies()) + if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf && + sctmf.hasMultipleFrequencies()) { List activeStates = new ArrayList<>(); activeStates.add(State.CONTROL); - modules.add(new ChannelRotationMonitor(activeStates, - ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay())); + modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences)); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/config/DecodeConfiguration.java b/src/main/java/io/github/dsheirer/module/decode/config/DecodeConfiguration.java index 8249ae289..9b45da7f9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/config/DecodeConfiguration.java +++ b/src/main/java/io/github/dsheirer/module/decode/config/DecodeConfiguration.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.config; @@ -34,8 +31,7 @@ import io.github.dsheirer.module.decode.ltrstandard.DecodeConfigLTRStandard; import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327; import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM; -import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1; -import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2; +import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25; import io.github.dsheirer.module.decode.passport.DecodeConfigPassport; import io.github.dsheirer.source.tuner.channel.ChannelSpecification; @@ -47,10 +43,9 @@ @JsonSubTypes.Type(value = DecodeConfigLTRStandard.class, name = "decodeConfigLTRStandard"), @JsonSubTypes.Type(value = DecodeConfigMPT1327.class, name = "decodeConfigMPT1327"), @JsonSubTypes.Type(value = DecodeConfigNBFM.class, name = "decodeConfigNBFM"), - @JsonSubTypes.Type(value = DecodeConfigP25Phase1.class, name = "decodeConfigP25Phase1"), - @JsonSubTypes.Type(value = DecodeConfigP25Phase2.class, name = "decodeConfigP25Phase2"), + @JsonSubTypes.Type(value = DecodeConfigP25.class, name = "decodeConfigP25"), @JsonSubTypes.Type(value = DecodeConfigPassport.class, name = "decodeConfigPassport"), - @JsonSubTypes.Type(value = DecodeConfigDMR.class, name = "decodeConfigDMR") + @JsonSubTypes.Type(value = DecodeConfigDMR.class, name = "decodeConfigDMR") }) @JacksonXmlRootElement(localName = "decode_configuration") public abstract class DecodeConfiguration extends Configuration @@ -58,10 +53,7 @@ public abstract class DecodeConfiguration extends Configuration public static final int DEFAULT_CALL_TIMEOUT_DELAY_SECONDS = 45; public static final int CALL_TIMEOUT_MINIMUM = 1; public static final int CALL_TIMEOUT_MAXIMUM = 180; - - public static final int TRAFFIC_CHANNEL_LIMIT_DEFAULT = 3; - public static final int TRAFFIC_CHANNEL_LIMIT_MINIMUM = 0; - public static final int TRAFFIC_CHANNEL_LIMIT_MAXIMUM = 30; + public static final int TRAFFIC_CHANNEL_LIMIT_DEFAULT = 20; public DecodeConfiguration() { diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java index dc3a34de0..4b939c29b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java @@ -124,7 +124,6 @@ public class DMRDecoderState extends TimeslotDecoderState private DMRNetworkConfigurationMonitor mNetworkConfigurationMonitor; private DMRTrafficChannelManager mTrafficChannelManager; private DecodeEvent mCurrentCallEvent; - private long mCurrentFrequency; private boolean mIgnoreCRCChecksums; private DMRDecoderState mSisterDecoderState; @@ -275,8 +274,7 @@ public void reset() { super.reset(); resetState(); - - mCurrentFrequency = 0; + setCurrentFrequency(0); } /** @@ -347,11 +345,11 @@ else if(isValid(message) && message.getTimeslot() == 0 && message instanceof LCM private void updateRestChannel(DMRChannel restChannel) { //Only respond if this is a standard/control channel (not a traffic channel). - if(mChannel.isStandardChannel() && mCurrentFrequency > 0 && + if(mChannel.isStandardChannel() && getCurrentFrequency() > 0 && restChannel.getDownlinkFrequency() > 0 && - restChannel.getDownlinkFrequency() != mCurrentFrequency && mTrafficChannelManager != null) + restChannel.getDownlinkFrequency() != getCurrentFrequency() && mTrafficChannelManager != null) { - mTrafficChannelManager.convertToTrafficChannel(mChannel, mCurrentFrequency, restChannel, + mTrafficChannelManager.convertToTrafficChannel(mChannel, getCurrentFrequency(), restChannel, mNetworkConfigurationMonitor); } } @@ -1400,9 +1398,9 @@ private void updateCurrentCall(DecodeEventType type, String details, long timest Event event = (mCurrentCallEvent == null ? Event.START : Event.CONTINUATION); //Create a repeater channel descriptor if we don't have one - if(mCurrentChannel == null && mCurrentFrequency > 0) + if(mCurrentChannel == null && getCurrentFrequency() > 0) { - mCurrentChannel = new DMRAbsoluteChannel(getTimeslot(), getTimeslot(), mCurrentFrequency, 0); + mCurrentChannel = new DMRAbsoluteChannel(getTimeslot(), getTimeslot(), getCurrentFrequency(), 0); } if(mCurrentCallEvent == null) @@ -1475,11 +1473,12 @@ public void receiveDecoderStateEvent(DecoderStateEvent event) resetState(); break; case NOTIFICATION_SOURCE_FREQUENCY: - long previous = mCurrentFrequency; - mCurrentFrequency = event.getFrequency(); - if(hasTrafficChannelManager()) + setCurrentFrequency(event.getFrequency()); + + //Only update the traffic channel manager if we're not a traffic channel. + if(hasTrafficChannelManager() && mChannel.isStandardChannel()) { - mTrafficChannelManager.setCurrentControlFrequency(previous, mCurrentFrequency, mChannel); + mTrafficChannelManager.setCurrentControlFrequency(getCurrentFrequency(), mChannel); } break; default: diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java index 8ec886fef..7da486918 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java @@ -132,13 +132,13 @@ public DMRTrafficChannelManager(Channel parentChannel) /** * Sets the current parent control channel frequency so that channel grants for the current frequency do not * produce an additional traffic channel allocation. - * @param previousControlFrequency for the current control channel (to remove from allocated channels) - * @param currentControlFrequency for current control channel (to add to allocated channels) + * @param previous for the current control channel (to remove from allocated channels) + * @param current for current control channel (to add to allocated channels) * @param channel for the current control channel */ - public void setCurrentControlFrequency(long previousControlFrequency, long currentControlFrequency, Channel channel) + public void processControlFrequencyUpdate(long previous, long current, Channel channel) { - if(previousControlFrequency == currentControlFrequency) + if(previous == current) { return; } @@ -147,18 +147,18 @@ public void setCurrentControlFrequency(long previousControlFrequency, long curre try { - Channel existing = mAllocatedChannelFrequencyMap.get(previousControlFrequency); + Channel existing = mAllocatedChannelFrequencyMap.get(previous); //Only remove the channel if it is non-null and it matches the current control channel. if(channel.equals(existing)) { //Unlock the frequency in the channel rotation monitor - getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(previousControlFrequency)); - mAllocatedChannelFrequencyMap.remove(previousControlFrequency); + getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(previous)); + mAllocatedChannelFrequencyMap.remove(previous); } - mAllocatedChannelFrequencyMap.put(currentControlFrequency, channel); - getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(currentControlFrequency)); + mAllocatedChannelFrequencyMap.put(current, channel); + getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(current)); } finally { diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DecodeConfigDMR.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DecodeConfigDMR.java index 22a161682..970d6c940 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DecodeConfigDMR.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DecodeConfigDMR.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -23,6 +23,7 @@ import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency; +import io.github.dsheirer.module.decode.dmr.message.DMRMessage; import io.github.dsheirer.module.decode.event.DecodeEvent; import io.github.dsheirer.source.tuner.channel.ChannelSpecification; import java.util.ArrayList; @@ -64,10 +65,7 @@ public int getTimeslotCount() @Override public int[] getTimeslots() { - int[] timeslots = new int[2]; - timeslots[0] = 1; - timeslots[1] = 2; - return timeslots; + return new int[]{DMRMessage.TIMESLOT_1, DMRMessage.TIMESLOT_2}; } @JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance") diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java index e7a897b0b..a9d19cba5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRMessage.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -28,6 +28,9 @@ */ public abstract class DMRMessage extends Message { + public static final int TIMESLOT_1 = 1; + public static final int TIMESLOT_2 = 2; + private CorrectedBinaryMessage mCorrectedBinaryMessage; private boolean mValid = true; private int mTimeslot; diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java index 4cc9cbf92..68f35a958 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -50,6 +50,28 @@ public DecodeEvent(DecodeEventType decodeEventType, long start) mTimeStart = start; } + /** + * Sets or changes the decode event type. + * @param type to set. + */ + public void setDecodeEventType(DecodeEventType type) + { + if(type == null) + { + throw new IllegalArgumentException("Decode event type cannot be null"); + } + + mDecodeEventType = type; + } + + /** + * Creates a snapshot of this decode event for offline analysis + */ + public DecodeEventSnapshot getSnapshot() + { + return new DecodeEventSnapshot(this); + } + /** * Creates a new decode event builder with the specified start timestamp. * @param decodeEventType for the event @@ -238,7 +260,8 @@ public String toString() sb.append(" IDS:").append(Joiner.on(",").join(mIdentifierCollection.getIdentifiers())); } sb.append(" DURATION:").append(getDuration()); - sb.append(" CHANNEL:").append(mChannelDescriptor); + sb.append(" CHANNEL:").append(getChannelDescriptor()); + sb.append(" TIMESLOT:").append(getTimeslot()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventDuplicateDetector.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventDuplicateDetector.java new file mode 100644 index 000000000..ff0497da8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventDuplicateDetector.java @@ -0,0 +1,136 @@ +/* + * ***************************************************************************** + * 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.event; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Provides duplicate decode event detection support + */ +public class DecodeEventDuplicateDetector +{ + private static final long EVENT_MAX_AGE_MILLISECONDS = Duration.ofMinutes(1).toMillis(); + private Map mTrackerMap = new HashMap<>(); + + /** + * Indicates if the event is a duplicate event. + * @param event to check + * @param timestamp of a current message to trigger time-based event age off. + * @return true if the event is a duplicate. + */ + public synchronized boolean isDuplicate(IDecodeEvent event, long timestamp) + { + //Null event types and voice call event types are not tracked by this detector + if(event.getEventType() == null || event.getEventType().isVoiceCallEvent()) + { + return false; + } + + if(!mTrackerMap.containsKey(event.getEventType())) + { + mTrackerMap.put(event.getEventType(), new DecodeEventTracker()); + } + + boolean duplicate = mTrackerMap.get(event.getEventType()).isDuplicate(event); + + for(DecodeEventTracker tracker: mTrackerMap.values()) + { + tracker.ageOff(timestamp); + } + + return duplicate; + } + + /** + * Tracks all decode events for a given decode event type + */ + private class DecodeEventTracker + { + private Map mDecodeEventMap = new HashMap<>(); + + /** + * Checks the event for duplicate + * @param event to check + * @return true if duplicate + */ + public boolean isDuplicate(IDecodeEvent event) + { + if(event.getIdentifierCollection().getToIdentifier() == null || event.getDetails() == null) + { + return false; + } + + String key = getKey(event); + + if(key == null || key.isEmpty()) + { + return false; + } + + if(mDecodeEventMap.containsKey(key)) + { + return true; + } + else + { + mDecodeEventMap.put(key, event); + } + + return false; + } + + /** + * Generates a unique event key that is a combinatio of the TO identifier value and the details of the event. + * @param event for the key + * @return generated key. + */ + private static String getKey(IDecodeEvent event) + { + return event.getIdentifierCollection().getToIdentifier().toString() + event.getDetails(); + } + + /** + * Removes all decode events from the duplicate detection map that are too old. + * @param timestamp for the current messaging. + */ + public void ageOff(long timestamp) + { + long threshold = timestamp - EVENT_MAX_AGE_MILLISECONDS; + List toRemove = new ArrayList<>(); + + for(Map.Entry entry: mDecodeEventMap.entrySet()) + { + if(entry.getValue() == null || entry.getValue().getTimeStart() < threshold) + { + toRemove.add(entry.getKey()); + } + } + + for(String key: toRemove) + { + mDecodeEventMap.remove(key); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventModel.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventModel.java index b677a1254..82be10c4a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventModel.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventModel.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -115,9 +115,9 @@ public Object getValueAt(int rowIndex, int columnIndex) if(channelDescriptor != null) { - if(event.hasTimeslot()) + if(event.hasTimeslot() && !event.toString().contains("TS")) { - return channelDescriptor + " TS:" + event.getTimeslot(); + return channelDescriptor + " TS" + event.getTimeslot(); } else { @@ -126,9 +126,9 @@ public Object getValueAt(int rowIndex, int columnIndex) } else { - if(event.hasTimeslot()) + if(event.hasTimeslot() && !event.toString().contains("TS")) { - return "TS:" + event.getTimeslot(); + return "TS" + event.getTimeslot(); } else { diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventSnapshot.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventSnapshot.java new file mode 100644 index 000000000..93751134c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventSnapshot.java @@ -0,0 +1,50 @@ +package io.github.dsheirer.module.decode.event; + +import io.github.dsheirer.identifier.IdentifierCollection; + +/** + * Snapshot of a decode event that also preserves the object instance hash code. + */ +public class DecodeEventSnapshot extends DecodeEvent +{ + private int mOriginalHashCode; + + /** + * Constructs an instance + * + * @param decodeEvent to snapshot + */ + public DecodeEventSnapshot(DecodeEvent decodeEvent) + { + super(decodeEvent.getEventType(), decodeEvent.getTimeStart()); + mOriginalHashCode = decodeEvent.hashCode(); + setDetails(decodeEvent.getDetails()); + setDuration(decodeEvent.getDuration()); + setProtocol(decodeEvent.getProtocol()); + setTimeslot(decodeEvent.getTimeslot()); + setChannelDescriptor(decodeEvent.getChannelDescriptor()); + if(decodeEvent.getIdentifierCollection() != null) + { + setIdentifierCollection(new IdentifierCollection(decodeEvent.getIdentifierCollection().getIdentifiers())); + } + } + + /** + * Hashcode of the original decode event for this snapshot (not this snapshot instance's hash code). + * @return + */ + public int getOriginalHashCode() + { + return mOriginalHashCode; + } + + /** + * Frequency for the channel descriptor + * @return frequency or zero. + */ + public long getFrequency() + { + return getChannelDescriptor() != null ? getChannelDescriptor().getDownlinkFrequency() : 0; + } + +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java index 3ce817b72..8686f6b63 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -55,6 +55,7 @@ public enum DecodeEventType DATA_CALL_ENCRYPTED("Encrypted Data Call"), DATA_PACKET("Data Packet"), DEREGISTER("Deregister"), + DYNAMIC_REGROUP("Dynamic Regroup"), EMERGENCY("EMERGENCY"), FUNCTION("Function"), GPS("GPS"), @@ -106,7 +107,7 @@ public enum DecodeEventType public static final EnumSet COMMANDS = EnumSet.of(DecodeEventType.ANNOUNCEMENT, DecodeEventType.STATION_ID, DecodeEventType.ACKNOWLEDGE, DecodeEventType.PAGE, DecodeEventType.QUERY, DecodeEventType.RADIO_CHECK, DecodeEventType.STATUS, DecodeEventType.COMMAND, DecodeEventType.EMERGENCY, - DecodeEventType.NOTIFICATION, DecodeEventType.FUNCTION); + DecodeEventType.NOTIFICATION, DecodeEventType.FUNCTION, DecodeEventType.DYNAMIC_REGROUP); /** * Data call event types for filtering @@ -129,6 +130,13 @@ public enum DecodeEventType public static final EnumSet OTHERS = EnumSet.copyOf(Arrays.stream(DecodeEventType.values()) .filter(decodeEventType -> !decodeEventType.isGrouped()).toList()); + /** + * Voice call event types. + */ + public static final EnumSet VOICE_CALL_EVENTS = EnumSet.of(CALL, CALL_ENCRYPTED, CALL_GROUP, + CALL_GROUP_ENCRYPTED, CALL_PATCH_GROUP, CALL_PATCH_GROUP_ENCRYPTED, CALL_INTERCONNECT, + CALL_INTERCONNECT_ENCRYPTED, CALL_UNIT_TO_UNIT, CALL_UNIT_TO_UNIT_ENCRYPTED); + /** * Constructor * @param label for the element @@ -149,12 +157,12 @@ public boolean isGrouped() } /** - * Indicates if this is a call event. - * @return true if this is a call event type. + * Indicates if this is a voice call event. + * @return true if this is a voice call event type. */ - public boolean isCallEvent() + public boolean isVoiceCallEvent() { - return VOICE_CALLS.contains(this) || VOICE_CALLS_ENCRYPTED.contains(this) || DATA_CALLS.contains(this); + return VOICE_CALL_EVENTS.contains(this); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPort.java b/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPort.java index 4c0b6cbdc..da09ac77f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPort.java +++ b/src/main/java/io/github/dsheirer/module/decode/ip/udp/UDPPort.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -37,7 +37,7 @@ public class UDPPort extends IntegerIdentifier */ public UDPPort(int value, Role role) { - super(value, IdentifierClass.NETWORK, Form.UDP_PORT, role); + super(value, IdentifierClass.USER_NETWORK_PORT, Form.UDP_PORT, role); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java index a810f814a..a88b02307 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java @@ -76,6 +76,32 @@ public MPT1327TrafficChannelManager(Channel parentChannel, ChannelMap channelMap mChannelMap = channelMap; } + /** + * Notification that the control frequency is updated. + * @param previous frequency for the control channel (to remove from allocated channels) + * @param current frequency for the control channel (to add to allocated channels) + * @param channel for the current control channel + */ + @Override + protected void processControlFrequencyUpdate(long previous, long current, Channel channel) + { + MPT1327Channel toRemove = null; + + for(MPT1327Channel mpt: mAllocatedTrafficChannelMap.keySet()) + { + if(mpt.getDownlinkFrequency() == current) + { + toRemove = mpt; + break; + } + } + + if(toRemove != null) + { + broadcast(new ChannelEvent(mAllocatedTrafficChannelMap.get(toRemove), ChannelEvent.Event.REQUEST_DISABLE)); + } + } + /** * Processes channel grants to allocate traffic channels and track overall channel usage. Generates * decode events for each new channel that is allocated. diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/IServiceOptionsProvider.java b/src/main/java/io/github/dsheirer/module/decode/p25/IServiceOptionsProvider.java new file mode 100644 index 000000000..bd161dc02 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/IServiceOptionsProvider.java @@ -0,0 +1,16 @@ +package io.github.dsheirer.module.decode.p25; + + +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; + +/** + * APCO25 message that exposes a service options configuration + */ +public interface IServiceOptionsProvider +{ + /** + * Service Options + * @return service options + */ + ServiceOptions getServiceOptions(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelGrantEvent.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelGrantEvent.java index 4b15afadb..0e4e694a3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelGrantEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25ChannelGrantEvent.java @@ -117,7 +117,7 @@ public P25ChannelGrantDecodeEventBuilder end(long timestamp) * Sets the channel descriptor for this event * @param channelDescriptor */ - public P25ChannelGrantDecodeEventBuilder channel(IChannelDescriptor channelDescriptor) + public P25ChannelGrantDecodeEventBuilder channelDescriptor(IChannelDescriptor channelDescriptor) { mChannelDescriptor = channelDescriptor; return this; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25FrequencyBandPreloadDataContent.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25FrequencyBandPreloadDataContent.java new file mode 100644 index 000000000..efe466286 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25FrequencyBandPreloadDataContent.java @@ -0,0 +1,22 @@ +package io.github.dsheirer.module.decode.p25; + +import io.github.dsheirer.controller.channel.event.PreloadDataContent; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; + +import java.util.Collection; + +/** + * Frequency bands (aka Identifier Update) to preload into a traffic channel. + */ +public class P25FrequencyBandPreloadDataContent extends PreloadDataContent> +{ + /** + * Constructs an instance + * + * @param data to preload + */ + public P25FrequencyBandPreloadDataContent(Collection data) + { + super(data); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java index 699d2b8b8..122f23da9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java @@ -18,6 +18,7 @@ */ package io.github.dsheirer.module.decode.p25; +import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.Channel.ChannelType; import io.github.dsheirer.controller.channel.ChannelEvent; @@ -25,17 +26,20 @@ import io.github.dsheirer.controller.channel.IChannelEventListener; import io.github.dsheirer.controller.channel.IChannelEventProvider; import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest; +import io.github.dsheirer.identifier.Form; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.identifier.MutableIdentifierCollection; -import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier; import io.github.dsheirer.identifier.patch.PatchGroupPreLoadDataContent; +import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.scramble.ScrambleParameterIdentifier; +import io.github.dsheirer.log.LoggingSuppressor; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.IMessageListener; -import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventDuplicateDetector; import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.event.IDecodeEvent; import io.github.dsheirer.module.decode.event.IDecodeEventProvider; @@ -44,24 +48,29 @@ import io.github.dsheirer.module.decode.p25.identifier.channel.P25Channel; import io.github.dsheirer.module.decode.p25.identifier.channel.P25P2Channel; import io.github.dsheirer.module.decode.p25.identifier.channel.P25P2ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.channel.StandardChannel; import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.NetworkStatusBroadcast; import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; import io.github.dsheirer.module.decode.traffic.TrafficChannelManager; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.config.SourceConfigTuner; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedTransferQueue; +import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,77 +94,120 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements IDecodeEventProvider, IChannelEventListener, IChannelEventProvider, IMessageListener { - private final static Logger mLog = LoggerFactory.getLogger(P25TrafficChannelManager.class); + private static final Logger mLog = LoggerFactory.getLogger(P25TrafficChannelManager.class); + private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(mLog); public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED"; public static final String MAX_TRAFFIC_CHANNELS_EXCEEDED = "MAX TRAFFIC CHANNELS EXCEEDED"; - private Queue mAvailablePhase1TrafficChannelQueue = new ConcurrentLinkedQueue<>(); + private Queue mAvailablePhase1TrafficChannelQueue = new LinkedTransferQueue<>(); private List mManagedPhase1TrafficChannels; - private Queue mAvailablePhase2TrafficChannelQueue = new ConcurrentLinkedQueue<>(); + private Queue mAvailablePhase2TrafficChannelQueue = new LinkedTransferQueue<>(); private List mManagedPhase2TrafficChannels; - - private Map mAllocatedTrafficChannelMap = new ConcurrentHashMap<>(); - private Map mTS0ChannelGrantEventMap = new ConcurrentHashMap<>(); - private Map mTS1ChannelGrantEventMap = new ConcurrentHashMap<>(); - + private Map mAllocatedTrafficChannelMap = new HashMap<>(); + private Map mTS1ChannelGrantEventMap = new HashMap<>(); + private Map mTS2ChannelGrantEventMap = new HashMap<>(); + private Map mFrequencyBandMap = new ConcurrentHashMap<>(); private Listener mChannelEventListener; private Listener mDecodeEventListener; - private TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor(); private Channel mParentChannel; private ScrambleParameters mPhase2ScrambleParameters; private Listener mMessageListener; - private boolean mIgnoreDataCalls; + private boolean mIdleNullProtect = false; + //Used only for data calls + private DecodeEventDuplicateDetector mDuplicateDetector = new DecodeEventDuplicateDetector(); + private ReentrantLock mLock = new ReentrantLock(); /** * Constructs an instance. - * @param parentChannel that owns this traffic channel manager + * @param parentChannel (ie control channel) that owns this traffic channel manager */ public P25TrafficChannelManager(Channel parentChannel) { mParentChannel = parentChannel; - if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase1) + if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase1 phase1) + { + mIgnoreDataCalls = phase1.getIgnoreDataCalls(); + createPhase1TrafficChannels(phase1.getTrafficChannelPoolSize(), phase1); + createPhase2TrafficChannels(phase1.getTrafficChannelPoolSize(), new DecodeConfigP25Phase2()); + } + else if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase2 phase2) + { + mIgnoreDataCalls = phase2.getIgnoreDataCalls(); + createPhase1TrafficChannels(phase2.getTrafficChannelPoolSize(), new DecodeConfigP25Phase1()); + createPhase2TrafficChannels(phase2.getTrafficChannelPoolSize(), phase2); + } + } + + /** + * Stores the frequency band (aka Identifier Update) to use for preload data in starting a new traffic channel. + * @param frequencyBand to store + */ + public void processFrequencyBand(IFrequencyBand frequencyBand) + { + mFrequencyBandMap.put(frequencyBand.getIdentifier(), frequencyBand); + } + + /** + * Notification that the control channel frequency is updated and removes any traffic channel that may be running + * against the same frequency. + * @param previous frequency (before this update) + * @param current frequency + * @param parentChannel configuration + */ + @Override + protected void processControlFrequencyUpdate(long previous, long current, Channel parentChannel) + { + if(previous == current) { - mIgnoreDataCalls = ((DecodeConfigP25Phase1)parentChannel.getDecodeConfiguration()).getIgnoreDataCalls(); + return; } - createPhase1TrafficChannels(); - createPhase2TrafficChannels(); + mLock.lock(); + + try + { + Channel channel = mAllocatedTrafficChannelMap.get(current); + + if(!parentChannel.equals(channel)) + { + broadcast(new ChannelEvent(mAllocatedTrafficChannelMap.get(current), Event.REQUEST_DISABLE)); + } + + //Store the control channel in the allocated channel map so that we don't allocate a traffic channel against it + mAllocatedTrafficChannelMap.put(current, parentChannel); + } + finally + { + mLock.unlock(); + } } /** * Creates up to the maximum number of traffic channels for use in allocating traffic channels. - * - * Note: this method uses lazy initialization and will only create the channels once. Subsequent calls will be ignored. + * @param trafficChannelPoolSize number of traffic channels to create + * @param decodeConfigP25Phase1 to use for each traffic channel */ - private void createPhase1TrafficChannels() + private void createPhase1TrafficChannels(int trafficChannelPoolSize, DecodeConfigP25Phase1 decodeConfigP25Phase1) { if(mManagedPhase1TrafficChannels == null) { - DecodeConfiguration decodeConfiguration = mParentChannel.getDecodeConfiguration(); List trafficChannelList = new ArrayList<>(); - if(decodeConfiguration instanceof DecodeConfigP25Phase1) + if(trafficChannelPoolSize > 0) { - DecodeConfigP25Phase1 p25DecodeConfig = (DecodeConfigP25Phase1)decodeConfiguration; - - int maxTrafficChannels = p25DecodeConfig.getTrafficChannelPoolSize(); - - if(maxTrafficChannels > 0) + for(int x = 0; x < trafficChannelPoolSize; x++) { - for(int x = 0; x < maxTrafficChannels; x++) - { - Channel trafficChannel = new Channel("T-" + mParentChannel.getName(), ChannelType.TRAFFIC); - trafficChannel.setAliasListName(mParentChannel.getAliasListName()); - trafficChannel.setSystem(mParentChannel.getSystem()); - trafficChannel.setSite(mParentChannel.getSite()); - trafficChannel.setDecodeConfiguration(p25DecodeConfig); - trafficChannel.setEventLogConfiguration(mParentChannel.getEventLogConfiguration()); - trafficChannel.setRecordConfiguration(mParentChannel.getRecordConfiguration()); - trafficChannelList.add(trafficChannel); - } + Channel trafficChannel = new Channel("T-" + mParentChannel.getName(), ChannelType.TRAFFIC); + trafficChannel.setAliasListName(mParentChannel.getAliasListName()); + trafficChannel.setSystem(mParentChannel.getSystem()); + trafficChannel.setSite(mParentChannel.getSite()); + trafficChannel.setDecodeConfiguration(decodeConfigP25Phase1); + trafficChannel.setEventLogConfiguration(mParentChannel.getEventLogConfiguration()); + trafficChannel.setRecordConfiguration(mParentChannel.getRecordConfiguration()); + trafficChannelList.add(trafficChannel); } } @@ -166,35 +218,27 @@ private void createPhase1TrafficChannels() /** * Creates up to the maximum number of traffic channels for use in allocating traffic channels. - * - * Note: this method uses lazy initialization and will only create the channels once. Subsequent calls will be ignored. + * @param trafficChannelPoolSize number of traffic channels to create + * @param decodeConfiguration for the parent channel */ - private void createPhase2TrafficChannels() + private void createPhase2TrafficChannels(int trafficChannelPoolSize, DecodeConfiguration decodeConfiguration) { if(mManagedPhase2TrafficChannels == null) { - DecodeConfiguration decodeConfiguration = mParentChannel.getDecodeConfiguration(); List trafficChannelList = new ArrayList<>(); - if(decodeConfiguration instanceof DecodeConfigP25Phase1) + if(trafficChannelPoolSize > 0) { - DecodeConfigP25Phase1 p25DecodeConfig = (DecodeConfigP25Phase1)decodeConfiguration; - - int maxTrafficChannels = p25DecodeConfig.getTrafficChannelPoolSize(); - - if(maxTrafficChannels > 0) + for(int x = 0; x < trafficChannelPoolSize; x++) { - for(int x = 0; x < maxTrafficChannels; x++) - { - Channel trafficChannel = new Channel("T-" + mParentChannel.getName(), ChannelType.TRAFFIC); - trafficChannel.setAliasListName(mParentChannel.getAliasListName()); - trafficChannel.setSystem(mParentChannel.getSystem()); - trafficChannel.setSite(mParentChannel.getSite()); - trafficChannel.setDecodeConfiguration(new DecodeConfigP25Phase2()); - trafficChannel.setEventLogConfiguration(mParentChannel.getEventLogConfiguration()); - trafficChannel.setRecordConfiguration(mParentChannel.getRecordConfiguration()); - trafficChannelList.add(trafficChannel); - } + Channel trafficChannel = new Channel("T-" + mParentChannel.getName(), ChannelType.TRAFFIC); + trafficChannel.setAliasListName(mParentChannel.getAliasListName()); + trafficChannel.setSystem(mParentChannel.getSystem()); + trafficChannel.setSite(mParentChannel.getSite()); + trafficChannel.setDecodeConfiguration(decodeConfiguration); + trafficChannel.setEventLogConfiguration(mParentChannel.getEventLogConfiguration()); + trafficChannel.setRecordConfiguration(mParentChannel.getRecordConfiguration()); + trafficChannelList.add(trafficChannel); } } @@ -210,46 +254,548 @@ public void broadcast(DecodeEvent decodeEvent) { if(mDecodeEventListener != null) { + if(decodeEvent.getEventType() == DecodeEventType.DATA_CALL && + mDuplicateDetector.isDuplicate(decodeEvent, System.currentTimeMillis())) + { + return; + } + mDecodeEventListener.receive(decodeEvent); } } /** - * Processes channel grants to allocate traffic channels and track overall channel usage. Generates - * decode events for each new channel that is allocated. + * Processes a P25 Phase 2 channel update for any channel. If the initial channel grant was not detected, invokes + * the process channel grant method to auto-create the channel. + * + * For the update message, we normally only get the TO talkgroup value, so we'll do a comparison of the event using + * just the TO identifier. + * + * @param channel where the activity is taking place. + * @param serviceOptions for the call + * @param ic identifier collection + * @param macOpcode for the update message + * @param timestamp of the message + */ + public void processP2ChannelUpdate(APCO25Channel channel, ServiceOptions serviceOptions, + IdentifierCollection ic, MacOpcode macOpcode, long timestamp) + { + if(channel.getDownlinkFrequency() > 0) + { + mLock.lock(); + + try + { + boolean processing = mAllocatedTrafficChannelMap.containsKey(channel.getDownlinkFrequency()); + + if(!processing) + { + processP2ChannelGrant(channel, serviceOptions, ic, macOpcode, timestamp); + } + } + finally + { + mLock.unlock(); + } + } + } + + /** + * Closes the call event for the specified channel frequency and timeslot. + * @param frequency for the channel + * @param timeslot for the channel. + * @param isIdleNull to indicate if this close event is trigged by an IDLE/NULL message + */ + public void closeP2CallEvent(long frequency, int timeslot, long timestamp, boolean isIdleNull) + { + /** + * Hack: L3Harris systems can issue a channel grant on control/TS1 which creates an event for TS2 and then the + * next message for TS2 is an IDLE/NULL which causes the event that was just created to be closed. So, we set + * a protect flag on the channel grant and then ignore the first IDLE/NULL triggered close event call, to + * protect the just created event. + */ + if(isIdleNull && mIdleNullProtect) + { + mIdleNullProtect = false; + return; + } + + mLock.lock(); + + try + { + DecodeEvent event; + + if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) + { + event = mTS1ChannelGrantEventMap.remove(frequency); + } + else + { + event = mTS2ChannelGrantEventMap.remove(frequency); + } + + if(event != null) + { + event.end(timestamp); + broadcast(event); + } + } + finally + { + mLock.unlock(); + } + } + + /** + * Updates an identifier for an ongoing call event on the frequency and timeslot. + * + * Note: if this manager does not have an existing call event for the frequency and timeslot, the update is ignored + * because we don't have enough detail to create a call event. + * + * This is used primarily to add encryption, GPS, or a talker alias, but can be used for any identifier update. + * + * @param frequency for the call event + * @param timeslot for the call event + * @param identifier to update within the event. + * @param timestamp for the update + */ + public void processP2CurrentUser(long frequency, int timeslot, Identifier identifier, long timestamp) + { + mLock.lock(); + + try + { + DecodeEvent event = timeslot == P25P1Message.TIMESLOT_1 ? + mTS1ChannelGrantEventMap.get(frequency) : mTS2ChannelGrantEventMap.get(frequency); + + if(event != null) + { + if(!event.getIdentifierCollection().hasIdentifier(identifier)) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(event.getIdentifierCollection() + .getIdentifiers()); + mic.update(identifier); + event.setIdentifierCollection(mic); + } + + event.update(timestamp); + broadcast(event); + } + } + finally + { + mLock.unlock(); + } + } + + + + /** + * Processes a call on the current channel + * @param frequency of the current channel + * @param timeslot of the current channel + * @param serviceOptions for the call + * @param ic for the call + * @param timestamp for the message that is being processed + * @return + */ + public IChannelDescriptor processP2CurrentUser(long frequency, int timeslot, IChannelDescriptor channelDescriptor, + ServiceOptions serviceOptions, MacOpcode macOpcode, + IdentifierCollection ic, long timestamp, String additionalDetails) + { + mLock.lock(); + + try + { + DecodeEvent event = timeslot == P25P1Message.TIMESLOT_1 ? + mTS1ChannelGrantEventMap.get(frequency) : mTS2ChannelGrantEventMap.get(frequency); + + if(event != null) + { + if(isSameCallFull(event.getIdentifierCollection(), ic)) + { + boolean updateRequired = false; + + for(Identifier identifier: ic.getIdentifiers()) + { + if(!event.getIdentifierCollection().hasIdentifier(identifier)) + { + updateRequired = true; + break; + } + } + + if(updateRequired) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(event.getIdentifierCollection().getIdentifiers()); + for(Identifier identifier: ic.getIdentifiers()) + { + mic.update(identifier); + } + + event.setIdentifierCollection(mic); + } + + event.update(timestamp); + event.setDecodeEventType(getEventType(macOpcode, serviceOptions, event.getEventType())); + + if(additionalDetails != null) + { + if(event.getDetails() == null) + { + event.setDetails(additionalDetails); + } + else if(!event.getDetails().endsWith(additionalDetails)) + { + event.setDetails(event.getDetails() + " " + additionalDetails); + } + } + + if(event.getChannelDescriptor() == null) + { + event.setChannelDescriptor(channelDescriptor); + } + + broadcast(event); + return event.getChannelDescriptor(); + } + } + + //Create a new event for the current call. + DecodeEventType eventType = getEventType(macOpcode, serviceOptions, null); + P25ChannelGrantEvent callEvent = P25ChannelGrantEvent.builder(eventType, timestamp, serviceOptions) + .channelDescriptor(channelDescriptor) + .details(additionalDetails != null ? additionalDetails : "PHASE 2 CALL " + + (serviceOptions != null ? serviceOptions : "")) + .identifiers(ic) + .build(); + + if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) + { + mTS1ChannelGrantEventMap.put(frequency, callEvent); + } + else + { + mTS2ChannelGrantEventMap.put(frequency, callEvent); + } + + broadcast(callEvent); + return null; + } + finally + { + mLock.unlock(); + } + } + + /** + * Processes phase 2 channel grants to allocate traffic channels and track overall channel usage. Generates and + * tracks decode events for each new channel that is allocated. * * @param apco25Channel for the traffic channel * @param serviceOptions for the traffic channel - optional can be null - * @param identifierCollection associated with the channel grant - * @param opcode to identify the call type for the event description + * @param ic associated with the channel grant + * @param macOpcode to identify the call type for the event description */ - public void processChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, - IdentifierCollection identifierCollection, Opcode opcode, long timestamp) + public void processP2ChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, + IdentifierCollection ic, MacOpcode macOpcode, long timestamp) { - if(apco25Channel.isTDMAChannel()) + mLock.lock(); + + try { - if(apco25Channel.getTimeslotCount() == 2) + DecodeEventType decodeEventType = getEventType(macOpcode, serviceOptions, null); + boolean isDataChannelGrant = macOpcode.isDataChannelGrant(); + + if(apco25Channel.isTDMAChannel()) { - //Data channels may be granted as a phase 2 channel grant but are still phase 1 channels - if(opcode.isDataChannelGrant()) + if(apco25Channel.getTimeslotCount() == 2) { - APCO25Channel phase1Channel = convertPhase2ToPhase1(apco25Channel); - processPhase1ChannelGrant(phase1Channel, serviceOptions, identifierCollection, opcode, timestamp); + //Data channels may be granted as a phase 2 channel grant but are still phase 1 channels + if(macOpcode.isDataChannelGrant()) + { + APCO25Channel phase1Channel = convertPhase2ToPhase1(apco25Channel); + processPhase1ChannelGrant(phase1Channel, serviceOptions, ic, decodeEventType, + isDataChannelGrant, timestamp); + } + else + { + processPhase2ChannelGrant(apco25Channel, serviceOptions, ic, decodeEventType, + isDataChannelGrant, timestamp); + + //Hack: set the IDLE/NULL protect flag so that this event doesn't get immediately closed on + //L3Harris control channels. + mIdleNullProtect = true; + } } else { - processPhase2ChannelGrant(apco25Channel, serviceOptions, identifierCollection, opcode, timestamp); + mLog.warn("Cannot process TDMA channel grant - unrecognized timeslot count: " + + apco25Channel.getTimeslotCount()); } } else { - mLog.warn("Cannot process TDMA channel grant - unrecognized timeslot count: " + - apco25Channel.getTimeslotCount()); + processPhase1ChannelGrant(apco25Channel, serviceOptions, ic, decodeEventType, isDataChannelGrant, + timestamp); } + } - else + finally + { + mLock.unlock(); + } + } + + /** + * Processes phase 1 channel grants to allocate traffic channels and track overall channel usage. Generates and + * tracks decode events for each new channel that is allocated. + * + * @param apco25Channel for the traffic channel + * @param serviceOptions for the traffic channel - optional can be null + * @param identifierCollection associated with the channel grant + * @param opcode to identify the call type for the event description + */ + public void processP1ChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, + IdentifierCollection identifierCollection, Opcode opcode, long timestamp) + { + mLock.lock(); + + try + { + DecodeEventType decodeEventType = getEventType(opcode, serviceOptions, null); + boolean isDataChannelGrant = opcode != null && opcode.isDataChannelGrant(); + + if(apco25Channel.isTDMAChannel()) + { + processPhase2ChannelGrant(apco25Channel, serviceOptions, identifierCollection, decodeEventType, + isDataChannelGrant, timestamp); + } + else + { + processPhase1ChannelGrant(apco25Channel, serviceOptions, identifierCollection, decodeEventType, + isDataChannelGrant, timestamp); + } + } + finally { - processPhase1ChannelGrant(apco25Channel, serviceOptions, identifierCollection, opcode, timestamp); + mLock.unlock(); + } + } + + /** + * Updates an identifier for an ongoing call event on the frequency. + * + * Note: if this manager does not have an existing call event for the frequency, the update is ignored + * because we don't have enough detail to create a call event. + * + * This is used primarily to add encryption, GPS, or a talker alias, but can be used for any identifier update. + * + * @param frequency for the call event + * @param identifier to update within the event. + * @param timestamp for the update + */ + public void processP1CurrentUser(long frequency, Identifier identifier, long timestamp) + { + mLock.lock(); + + try + { + DecodeEvent event = mTS1ChannelGrantEventMap.get(frequency); + + if(event != null) + { + if(!event.getIdentifierCollection().hasIdentifier(identifier)) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(event.getIdentifierCollection() + .getIdentifiers()); + mic.update(identifier); + event.setIdentifierCollection(mic); + } + + //Add the encryption key to the call event details. + if(identifier instanceof EncryptionKeyIdentifier eki && eki.isEncrypted()) + { + if(event.getDetails() == null) + { + event.setDetails(eki.toString()); + } + else if(!event.getDetails().contains(eki.toString())) + { + event.setDetails(event.getDetails() + " " + eki); + } + } + + event.update(timestamp); + broadcast(event); + } + } + finally + { + mLock.unlock(); + } + } + + /** + * Processes a call on the current channel + * @param frequency of the current channel + * @param channelDescriptor for the current channel + * @param decodeEventType to use for the event if it doesn't already exist + * @param serviceOptions for the call + * @param ic identifiers for the call + * @param timestamp for the message that is being processed + * @return channel descriptor for the event or null + */ + public IChannelDescriptor processP1CurrentUser(long frequency, IChannelDescriptor channelDescriptor, + DecodeEventType decodeEventType, ServiceOptions serviceOptions, + IdentifierCollection ic, long timestamp, String additionalDetails) + { + mLock.lock(); + + try + { + DecodeEvent event = mTS1ChannelGrantEventMap.get(frequency); + + if(event != null) + { + if(isSameCallFull(event.getIdentifierCollection(), ic)) + { + boolean updateRequired = false; + + for(Identifier identifier: ic.getIdentifiers()) + { + if(!event.getIdentifierCollection().hasIdentifier(identifier)) + { + updateRequired = true; + break; + } + } + + if(updateRequired) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(event.getIdentifierCollection().getIdentifiers()); + for(Identifier identifier: ic.getIdentifiers()) + { + mic.update(identifier); + } + + event.setIdentifierCollection(mic); + } + + event.update(timestamp); + + if(additionalDetails != null) + { + if(event.getDetails() == null) + { + event.setDetails(additionalDetails); + } + else if(!event.getDetails().endsWith(additionalDetails)) + { + event.setDetails(event.getDetails() + " " + additionalDetails); + } + } + + broadcast(event); + return event.getChannelDescriptor(); + } + } + + if(channelDescriptor == null && frequency > 0) + { + channelDescriptor = new StandardChannel(frequency); + } + + //Create a new event for the current call. + P25ChannelGrantEvent callEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + .channelDescriptor(channelDescriptor) + .details(additionalDetails != null ? additionalDetails : "PHASE 1 CALL " + + (serviceOptions != null ? serviceOptions : "")) + .identifiers(ic) + .build(); + + mTS1ChannelGrantEventMap.put(frequency, callEvent); + broadcast(callEvent); + return null; + } + finally + { + mLock.unlock(); + } + } + + /** + * Processes a P25 Phase 1 channel update for any channel. If the initial channel grant was not detected, invokes + * the process channel grant method to auto-create the channel. + * + * For the update message, we normally only get the TO talkgroup value, so we'll do a comparison of the event using + * just the TO identifier. + * + * @param channel where the activity is taking place. + * @param serviceOptions for the call + * @param ic identifier collection + * @param opcode for the update message + * @param timestamp of the message + */ + public void processP1ChannelUpdate(APCO25Channel channel, ServiceOptions serviceOptions, + IdentifierCollection ic, Opcode opcode, long timestamp) + { + + mLock.lock(); + + try + { + DecodeEvent event = null; + + if(channel.isTDMAChannel() && channel.getTimeslot() == P25P1Message.TIMESLOT_2) + { + event = mTS2ChannelGrantEventMap.get(channel.getDownlinkFrequency()); + } + else + { + event = mTS1ChannelGrantEventMap.get(channel.getDownlinkFrequency()); + } + + //If we have an event, update it. Otherwise, make sure we have the traffic channel allocated + if(event != null && isSameCallUpdate(event.getIdentifierCollection(), ic)) + { + event.update(timestamp); + broadcast(event); + } + else if(channel.getDownlinkFrequency() > 0 && + !mAllocatedTrafficChannelMap.containsKey(channel.getDownlinkFrequency())) + { + processP1ChannelGrant(channel, serviceOptions, ic, opcode, timestamp); + } + } + finally + { + mLock.unlock(); + } + } + + /** + * Closes the call event for the specified channel frequency. + * @param frequency for the channel + */ + public void closeP1CallEvent(long frequency, long timestamp) + { + mLock.lock(); + + try + { + DecodeEvent event = mTS1ChannelGrantEventMap.remove(frequency); + + if(event != null) + { + event.end(timestamp); + broadcast(event); + } + } + finally + { + mLock.unlock(); } } @@ -257,38 +803,42 @@ public void processChannelGrant(APCO25Channel apco25Channel, ServiceOptions serv * Processes Phase 1 channel grants to allocate traffic channels and track overall channel usage. Generates * decode events for each new channel that is allocated. * + * Note: protected thread access to this method is controlled by the calling method. + * * @param apco25Channel for the traffic channel * @param serviceOptions for the traffic channel - optional can be null * @param identifierCollection associated with the channel grant - * @param opcode to identify the call type for the event description + * @param decodeEventType to use + * @param isDataChannelGrant indicator if this is a data channel grant + * @param timestamp for the grant event. */ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, - IdentifierCollection identifierCollection, Opcode opcode, long timestamp) + IdentifierCollection identifierCollection, DecodeEventType decodeEventType, + boolean isDataChannelGrant, long timestamp) { long frequency = apco25Channel.getDownlinkFrequency(); - DecodeEventType decodeEventType = getEventType(opcode, serviceOptions); - P25ChannelGrantEvent event = mTS0ChannelGrantEventMap.get(frequency); + P25ChannelGrantEvent event = mTS1ChannelGrantEventMap.get(frequency); - if(event != null && isSameCall(identifierCollection, event.getIdentifierCollection())) + if(event != null && isSameCallUpdate(identifierCollection, event.getIdentifierCollection())) { - Identifier from = getIdentifier(identifierCollection, Role.FROM); + Identifier from = identifierCollection.getFromIdentifier(); if(from != null) { - Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM); - if(currentFrom != null && !Objects.equals(from, currentFrom)) + Identifier currentFrom = event.getIdentifierCollection().getFromIdentifier(); + if(currentFrom != null && !from.equals(currentFrom)) { event.end(timestamp); + broadcast(event); - P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) - .channel(apco25Channel) + event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + .channelDescriptor(apco25Channel) .details("CONTINUE - PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); - mTS0ChannelGrantEventMap.put(frequency, continuationGrantEvent); - broadcast(continuationGrantEvent); + mTS1ChannelGrantEventMap.put(frequency, event); } } @@ -298,13 +848,20 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio //Even though we have an event, the initial channel grant may have been rejected. Check to see if there //is a traffic channel allocated. If not, allocate one and update the event description. - if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !(mIgnoreDataCalls && opcode.isDataChannelGrant())) + if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !(mIgnoreDataCalls && isDataChannelGrant)) { Channel trafficChannel = mAvailablePhase1TrafficChannelQueue.poll(); if(trafficChannel != null) { - event.setDetails("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); + if(isDataChannelGrant) + { + event.setDetails("PHASE 1 DATA CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); + } + else + { + event.setDetails("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); + } event.setChannelDescriptor(apco25Channel); broadcast(event); SourceConfigTuner sourceConfig = new SourceConfigTuner(); @@ -316,9 +873,10 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio trafficChannel.setSourceConfiguration(sourceConfig); mAllocatedTrafficChannelMap.put(frequency, trafficChannel); - ChannelStartProcessingRequest startChannelRequest = - new ChannelStartProcessingRequest(trafficChannel, apco25Channel, identifierCollection); - startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection)); + ChannelStartProcessingRequest startChannelRequest = new ChannelStartProcessingRequest(trafficChannel, + apco25Channel, identifierCollection, this); + startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection, timestamp)); + startChannelRequest.addPreloadDataContent(new P25FrequencyBandPreloadDataContent(mFrequencyBandMap.values())); getInterModuleEventBus().post(startChannelRequest); } } @@ -326,26 +884,30 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio return; } - if(mIgnoreDataCalls && opcode.isDataChannelGrant()) + if(mIgnoreDataCalls && isDataChannelGrant && event == null) { - P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) - .channel(apco25Channel) - .details("DATA CALL IGNORED: " + (serviceOptions != null ? serviceOptions : "")) + event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + .channelDescriptor(apco25Channel) + .details("IGNORED: PHASE 1 DATA CALL " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); - - mTS0ChannelGrantEventMap.put(frequency, channelGrantEvent); - broadcast(channelGrantEvent); + mTS1ChannelGrantEventMap.put(frequency, event); + broadcast(event); return; } - P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) - .channel(apco25Channel) + event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + .channelDescriptor(apco25Channel) .details("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); - mTS0ChannelGrantEventMap.put(frequency, channelGrantEvent); + if(isDataChannelGrant) + { + event.setDetails("PHASE 1 DATA CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); + } + + mTS1ChannelGrantEventMap.put(frequency, event); //Allocate a traffic channel for the downlink frequency if one isn't already allocated if(!mAllocatedTrafficChannelMap.containsKey(frequency)) @@ -354,7 +916,7 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio if(trafficChannel == null) { - channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - IGNORED"); + event.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - " + (event.getDetails() != null ? event.getDetails() : "")); return; } @@ -368,26 +930,35 @@ private void processPhase1ChannelGrant(APCO25Channel apco25Channel, ServiceOptio mAllocatedTrafficChannelMap.put(frequency, trafficChannel); ChannelStartProcessingRequest startChannelRequest = - new ChannelStartProcessingRequest(trafficChannel, apco25Channel, identifierCollection); - startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection)); - getInterModuleEventBus().post(startChannelRequest); + new ChannelStartProcessingRequest(trafficChannel, apco25Channel, identifierCollection, this); + startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection, timestamp)); + startChannelRequest.addPreloadDataContent(new P25FrequencyBandPreloadDataContent(mFrequencyBandMap.values())); + + if(getInterModuleEventBus() != null) + { + getInterModuleEventBus().post(startChannelRequest); + } } - broadcast(channelGrantEvent); + broadcast(event); } - /** * Processes Phase 2 channel grants to allocate traffic channels and track overall channel usage. Generates * decode events for each new channel that is allocated. * + * Note: protected thread access to this method is controlled by the calling method. + * * @param apco25Channel for the traffic channel * @param serviceOptions for the traffic channel - optional can be null * @param identifierCollection associated with the channel grant - * @param opcode to identify the call type for the event description + * @param decodeEventType to use for the event. + * @param isDataChannelGrant indicator if this is a data channel grant + * @param timestamp for the event */ - private void processPhase2ChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, - IdentifierCollection identifierCollection, Opcode opcode, long timestamp) + private DecodeEvent processPhase2ChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, + IdentifierCollection identifierCollection, DecodeEventType decodeEventType, + boolean isDataChannelGrant, long timestamp) { if(mPhase2ScrambleParameters != null && identifierCollection instanceof MutableIdentifierCollection) { @@ -399,47 +970,46 @@ private void processPhase2ChannelGrant(APCO25Channel apco25Channel, ServiceOptio P25ChannelGrantEvent event = null; - if(timeslot == 0) + if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) { - event = mTS0ChannelGrantEventMap.get(frequency); + event = mTS1ChannelGrantEventMap.get(frequency); } - else if(timeslot == 1) + else if(timeslot == P25P1Message.TIMESLOT_2) { - event = mTS1ChannelGrantEventMap.get(frequency); + event = mTS2ChannelGrantEventMap.get(frequency); } else { mLog.error("Ignoring: Invalid timeslot [" + timeslot + "] detected for P25 Phase 2 Channel Grant."); - return; + return event; } identifierCollection.setTimeslot(timeslot); - DecodeEventType decodeEventType = getEventType(opcode, serviceOptions); - if(event != null && isSameCall(identifierCollection, event.getIdentifierCollection())) + if(event != null && isSameCallUpdate(identifierCollection, event.getIdentifierCollection())) { - Identifier from = getIdentifier(identifierCollection, Role.FROM); + Identifier from = identifierCollection.getFromIdentifier(); if(from != null) { - Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM); - if(currentFrom != null && !Objects.equals(from, currentFrom)) + Identifier currentFrom = event.getIdentifierCollection().getFromIdentifier(); + if(currentFrom != null && !from.equals(currentFrom)) { event.end(timestamp); P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) - .channel(apco25Channel) + .channelDescriptor(apco25Channel) .details("CONTINUE - PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); - if(timeslot == 0) + if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) { - mTS0ChannelGrantEventMap.put(frequency, continuationGrantEvent); + mTS1ChannelGrantEventMap.put(frequency, continuationGrantEvent); } else { - mTS1ChannelGrantEventMap.put(frequency, continuationGrantEvent); + mTS2ChannelGrantEventMap.put(frequency, continuationGrantEvent); } broadcast(continuationGrantEvent); @@ -448,11 +1018,15 @@ else if(timeslot == 1) //update the ending timestamp so that the duration value is correctly calculated event.update(timestamp); + + //Update the event type in case this they change from unencrypted to encrypted. + event.setDecodeEventType(decodeEventType); broadcast(event); //Even though we have an event, the initial channel grant may have been rejected. Check to see if there //is a traffic channel allocated. If not, allocate one and update the event description. - if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !(mIgnoreDataCalls && opcode.isDataChannelGrant())) + if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !(mIgnoreDataCalls && isDataChannelGrant) && + (getCurrentControlFrequency() != frequency)) { Channel trafficChannel = mAvailablePhase2TrafficChannelQueue.poll(); @@ -478,53 +1052,54 @@ else if(timeslot == 1) } ChannelStartProcessingRequest startChannelRequest = - new ChannelStartProcessingRequest(trafficChannel, apco25Channel, identifierCollection); - startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection)); + new ChannelStartProcessingRequest(trafficChannel, apco25Channel, identifierCollection, this); + startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection, timestamp)); + startChannelRequest.addPreloadDataContent(new P25FrequencyBandPreloadDataContent(mFrequencyBandMap.values())); getInterModuleEventBus().post(startChannelRequest); } } - return; + return event; } - if(mIgnoreDataCalls && opcode.isDataChannelGrant()) + if(mIgnoreDataCalls && isDataChannelGrant && event == null) { - P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) - .channel(apco25Channel) + event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + .channelDescriptor(apco25Channel) .details("PHASE 2 DATA CALL IGNORED: " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); - mTS0ChannelGrantEventMap.put(frequency, channelGrantEvent); - - broadcast(channelGrantEvent); - return; + //Phase 1 events get stored in TS1 only + mTS1ChannelGrantEventMap.put(frequency, event); + broadcast(event); + return event; } - P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) - .channel(apco25Channel) + event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + .channelDescriptor(apco25Channel) .details("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) .identifiers(identifierCollection) .build(); - if(timeslot == 0) + if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) { - mTS0ChannelGrantEventMap.put(frequency, channelGrantEvent); + mTS1ChannelGrantEventMap.put(frequency, event); } else { - mTS1ChannelGrantEventMap.put(frequency, channelGrantEvent); + mTS2ChannelGrantEventMap.put(frequency, event); } //Allocate a traffic channel for the downlink frequency if one isn't already allocated - if(!mAllocatedTrafficChannelMap.containsKey(apco25Channel.getDownlinkFrequency())) + if(!mAllocatedTrafficChannelMap.containsKey(frequency) && frequency != getCurrentControlFrequency()) { Channel trafficChannel = mAvailablePhase2TrafficChannelQueue.poll(); if(trafficChannel == null) { - channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - IGNORED"); - return; + event.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - IGNORED"); + return event; } SourceConfigTuner sourceConfig = new SourceConfigTuner(); @@ -537,62 +1112,170 @@ else if(timeslot == 1) mAllocatedTrafficChannelMap.put(frequency, trafficChannel); ChannelStartProcessingRequest startChannelRequest = - new ChannelStartProcessingRequest(trafficChannel, apco25Channel, identifierCollection); - startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection)); + new ChannelStartProcessingRequest(trafficChannel, apco25Channel, identifierCollection, this); + startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection, timestamp)); + startChannelRequest.addPreloadDataContent(new P25FrequencyBandPreloadDataContent(mFrequencyBandMap.values())); + + if(getInterModuleEventBus() == null) + { + return event; + } + getInterModuleEventBus().post(startChannelRequest); } - broadcast(channelGrantEvent); + broadcast(event); + return event; } /** - * Creates a call event type description for the specified opcode and service options + * Creates a Phase 2 call event type description for the specified opcode and service options + * @param macOpcode to evaluate + * @param serviceOptions for the call + * @param current decode event type (optional null). + * @return event type for the mac opcode. */ - private DecodeEventType getEventType(Opcode opcode, ServiceOptions serviceOptions) + private DecodeEventType getEventType(MacOpcode macOpcode, ServiceOptions serviceOptions, DecodeEventType current) { boolean encrypted = serviceOptions != null ? serviceOptions.isEncrypted() : false; DecodeEventType type = null; - switch(opcode) + switch(macOpcode) { - case OSP_GROUP_VOICE_CHANNEL_GRANT: - case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE: - case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + case PUSH_TO_TALK: + if(current != null) + { + type = current; + } + else + { + type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP; + } + break; + case TDMA_01_GROUP_VOICE_CHANNEL_USER_ABBREVIATED: + case TDMA_05_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_IMPLICIT: + case TDMA_21_GROUP_VOICE_CHANNEL_USER_EXTENDED: + case TDMA_25_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT: + case PHASE1_40_GROUP_VOICE_CHANNEL_GRANT_IMPLICIT: + case PHASE1_42_GROUP_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: + case PHASE1_C0_GROUP_VOICE_CHANNEL_GRANT_EXPLICIT: + case PHASE1_C3_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP; break; - case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + case MOTOROLA_80_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED: + case MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE: + case PHASE1_90_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED: + case MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED: + case MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT: + case MOTOROLA_A4_GROUP_REGROUP_CHANNEL_GRANT_EXPLICIT: + case MOTOROLA_A5_GROUP_REGROUP_CHANNEL_GRANT_UPDATE: + case L3HARRIS_B0_GROUP_REGROUP_EXPLICIT_ENCRYPTION_COMMAND: + type = encrypted ? DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED : DecodeEventType.CALL_PATCH_GROUP; + break; + + case TDMA_02_UNIT_TO_UNIT_VOICE_CHANNEL_USER_ABBREVIATED: + case TDMA_22_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: + case PHASE1_44_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_ABBREVIATED: + case PHASE1_46_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED: + case PHASE1_48_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_IMPLICIT: + case PHASE1_C4_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_VCH: + case PHASE1_C6_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_VCH: + case PHASE1_CF_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_LCCH: type = encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT; break; - case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + case TDMA_03_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: + case PHASE1_C8_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_EXPLICIT: type = encrypted ? DecodeEventType.CALL_INTERCONNECT_ENCRYPTED : DecodeEventType.CALL_INTERCONNECT; break; - case OSP_SNDCP_DATA_CHANNEL_GRANT: - case OSP_GROUP_DATA_CHANNEL_GRANT: - case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: + case PHASE1_54_SNDCP_DATA_CHANNEL_GRANT: + case L3HARRIS_A0_PRIVATE_DATA_CHANNEL_GRANT: + case L3HARRIS_AC_UNIT_TO_UNIT_DATA_CHANNEL_GRANT: type = encrypted ? DecodeEventType.DATA_CALL_ENCRYPTED : DecodeEventType.DATA_CALL; break; + } - case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT: - case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE: - type = encrypted ? DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED : DecodeEventType.CALL_PATCH_GROUP; - break; + if(type == null) + { + LOGGING_SUPPRESSOR.error(macOpcode.name(), 2, "Unrecognized MAC opcode for determining " + + "decode event type: " + macOpcode.name()); + type = current; } if(type == null) { - mLog.error("Unrecognized opcode for determining decode event type: " + opcode.name()); type = DecodeEventType.CALL; } return type; } + + + /** + * Creates a call event type description for the specified opcode and service options + */ + private DecodeEventType getEventType(Opcode opcode, ServiceOptions serviceOptions, DecodeEventType current) + { + boolean encrypted = serviceOptions != null ? serviceOptions.isEncrypted() : false; + + DecodeEventType type = null; + + if(opcode != null) + { + switch(opcode) + { + case OSP_GROUP_VOICE_CHANNEL_GRANT: + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE: + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP; + break; + + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + type = encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT; + break; + + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + type = encrypted ? DecodeEventType.CALL_INTERCONNECT_ENCRYPTED : DecodeEventType.CALL_INTERCONNECT; + break; + + case OSP_SNDCP_DATA_CHANNEL_GRANT: + case OSP_GROUP_DATA_CHANNEL_GRANT: + case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: + type = encrypted ? DecodeEventType.DATA_CALL_ENCRYPTED : DecodeEventType.DATA_CALL; + break; + + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT: + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE: + type = encrypted ? DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED : DecodeEventType.CALL_PATCH_GROUP; + break; + } + } + + if(type == null) + { + type = current; + + if(opcode != null) + { + LOGGING_SUPPRESSOR.error(opcode.name(), 2, "Unrecognized opcode for determining decode " + + "event type: " + opcode.name()); + } + } + + if(type == null) + { + type = encrypted ? DecodeEventType.CALL_ENCRYPTED : DecodeEventType.CALL; + } + + return type; + } + /** * Channel event listener to receive notifications that a traffic channel has ended processing and we * can reclaim the traffic channel for reuse. @@ -635,35 +1318,61 @@ public void removeChannelEventListener() } /** - * Compares the TO role identifier(s) from each collection for equality + * Compares the TO role identifier(s) from each collection for equality. This is normally used to compare a call + * update where we only compare the TO role. * * @param collection1 containing a TO identifier * @param collection2 containing a TO identifier * @return true if both collections contain a TO identifier and the TO identifiers are the same value */ - private boolean isSameCall(IdentifierCollection collection1, IdentifierCollection collection2) + private boolean isSameCallUpdate(IdentifierCollection collection1, IdentifierCollection collection2) { - Identifier toIdentifier1 = getIdentifier(collection1, Role.TO); - Identifier toIdentifier2 = getIdentifier(collection2, Role.TO); - return Objects.equals(toIdentifier1, toIdentifier2); + Identifier toIdentifier1 = collection1.getToIdentifier(); + Identifier toIdentifier2 = collection2.getToIdentifier(); + return toIdentifier1 != null && toIdentifier1.equals(toIdentifier2); } /** - * Retrieves the first identifier with a TO role. + * Compares the TO role identifier(s) from each collection for equality. This is normally used to compare a call + * update where we only compare the TO role. * - * @param collection containing a TO identifier - * @return TO identifier or null + * @param collection1 containing a TO identifier + * @param collection2 containing a TO identifier + * @return true if both collections contain a TO identifier and the TO identifiers are the same value */ - private Identifier getIdentifier(IdentifierCollection collection, Role role) + private boolean isSameCallFull(IdentifierCollection collection1, IdentifierCollection collection2) { - List identifiers = collection.getIdentifiers(role); + Identifier to1 = collection1.getToIdentifier(); + Identifier to2 = collection2.getToIdentifier(); - if(identifiers.size() >= 1) + if(to1 != null && to1.equals(to2)) { - return identifiers.get(0); + Identifier from1 = collection1.getFromIdentifier(); + + //If the FROM identifier hasn't yet been established, then this is the same call. We also ignore the + //talker alias as a call identifier since on L3Harris systems they can transmit the talker alias before + //they transmit the radio ID. + if(from1 == null || from1.getForm() == Form.TALKER_ALIAS) + { + return true; + } + + Identifier from2 = collection2.getFromIdentifier(); + + if(from2 != null && from2.getForm() == Form.TALKER_ALIAS) + { + return true; + } + + if(from2 instanceof RadioIdentifier radio && radio.getValue() == 0) + { + return true; + } + + return from1.equals(from2); } - return null; + return false; } /** @@ -697,9 +1406,6 @@ public void start() @Override public void stop() { - mAvailablePhase1TrafficChannelQueue.clear(); - mAvailablePhase2TrafficChannelQueue.clear(); - List channels = new ArrayList<>(mAllocatedTrafficChannelMap.values()); //Issue a disable request for each traffic channel @@ -709,8 +1415,12 @@ public void stop() broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE)); } - mTS0ChannelGrantEventMap.clear(); + mAvailablePhase1TrafficChannelQueue.clear(); + mAvailablePhase2TrafficChannelQueue.clear(); +//TODO: debug + mLog.info("TCM - Stopping and clearing channel grant event maps"); mTS1ChannelGrantEventMap.clear(); + mTS2ChannelGrantEventMap.clear(); } /** @@ -769,31 +1479,11 @@ else if(toConvert instanceof P25P2Channel) } /** - * Monitors channel teardown events to detect when traffic channel processing has ended. Reclaims the - * channel instance for reuse by future traffic channel grants. + * Monitors channel teardown events to detect when traffic channel processing has ended or channel start has been + * rejected. Reclaims the traffic channel instance for reuse by future traffic channel grants. */ public class TrafficChannelTeardownMonitor implements Listener { - /** - * Removes an allocated traffic channel and adds it to the available channel queue - * @param channel to reset - * @param frequency of the channel - * @param isPhase1 true or false if it is a phase 2 channel - */ - private void resetTrafficChannel(Channel channel, long frequency, boolean isPhase1) - { - mAllocatedTrafficChannelMap.remove(frequency); - - if(isPhase1) - { - mAvailablePhase1TrafficChannelQueue.add(channel); - } - else - { - mAvailablePhase2TrafficChannelQueue.add(channel); - } - } - /** * Process channel events from the ChannelProcessingManager to account for owned child traffic channels. * Note: this method sees events for ALL channels and not just P25 channels managed by this instance. @@ -801,15 +1491,15 @@ private void resetTrafficChannel(Channel channel, long frequency, boolean isPhas * @param channelEvent to process */ @Override - public synchronized void receive(ChannelEvent channelEvent) + public void receive(ChannelEvent channelEvent) { Channel channel = channelEvent.getChannel(); - if(channel.isTrafficChannel()) + if(mManagedPhase1TrafficChannels.contains(channel)) { - boolean isPhase1 = channel.getDecodeConfiguration().getDecoderType() == DecoderType.P25_PHASE1; + mLock.lock(); - if(isPhase1 ? mManagedPhase1TrafficChannels.contains(channel) : mManagedPhase2TrafficChannels.contains(channel)) + try { switch(channelEvent.getEvent()) { @@ -819,10 +1509,10 @@ public synchronized void receive(ChannelEvent channelEvent) .filter(entry -> entry.getValue() == channel) .map(Map.Entry::getKey) .findFirst() - .ifPresent(frequencyToRemove -> { - resetTrafficChannel(channel, frequencyToRemove, isPhase1); - mTS0ChannelGrantEventMap.remove(frequencyToRemove); - mTS1ChannelGrantEventMap.remove(frequencyToRemove); + .ifPresent(frequency -> { + mAllocatedTrafficChannelMap.remove(frequency); + mTS1ChannelGrantEventMap.remove(frequency); + mAvailablePhase1TrafficChannelQueue.add(channel); }); break; case NOTIFICATION_PROCESSING_START_REJECTED: @@ -831,24 +1521,80 @@ public synchronized void receive(ChannelEvent channelEvent) .map(Map.Entry::getKey) .findFirst() .ifPresent(rejectedFrequency -> { - resetTrafficChannel(channel, rejectedFrequency, isPhase1); + mAllocatedTrafficChannelMap.remove(rejectedFrequency); + mAvailablePhase1TrafficChannelQueue.add(channel); - P25ChannelGrantEvent event = mTS0ChannelGrantEventMap.remove(rejectedFrequency); + //Leave the event in the map so that it doesn't get recreated. The channel + //processing manager set the 'tuner not available' in the details already + P25ChannelGrantEvent event = mTS1ChannelGrantEventMap.get(rejectedFrequency); - if(event == null) + if(event != null && !event.getDetails().contains(CHANNEL_START_REJECTED)) { - event = mTS1ChannelGrantEventMap.remove(rejectedFrequency); + event.setDetails(CHANNEL_START_REJECTED + " " + channelEvent.getDescription() + + (event.getDetails() != null ? " - " + event.getDetails() : "")); } + broadcast(event); + }); + break; + } + } + finally + { + mLock.unlock(); + } + } + else if(mManagedPhase2TrafficChannels.contains(channel)) + { + mLock.lock(); - if (event != null) + try + { + switch(channelEvent.getEvent()) + { + case NOTIFICATION_PROCESSING_STOP: + mAllocatedTrafficChannelMap.entrySet() + .stream() + .filter(entry -> entry.getValue() == channel) + .map(Map.Entry::getKey) + .findFirst() + .ifPresent(frequency -> { + mAllocatedTrafficChannelMap.remove(frequency); + mAvailablePhase2TrafficChannelQueue.add(channel); + mTS1ChannelGrantEventMap.remove(frequency); + }); + break; + case NOTIFICATION_PROCESSING_START_REJECTED: + mAllocatedTrafficChannelMap.entrySet().stream() + .filter(entry -> entry.getValue() == channel) + .map(Map.Entry::getKey) + .findFirst() + .ifPresent(rejectedFrequency -> { + mAllocatedTrafficChannelMap.remove(rejectedFrequency); + mAvailablePhase1TrafficChannelQueue.add(channel); + + //Leave the event in the map so that it doesn't get recreated. The channel + //processing manager set the 'tuner not available' in the details already + P25ChannelGrantEvent event1 = mTS1ChannelGrantEventMap.get(rejectedFrequency); + if (event1 != null) + { + broadcast(event1); + } + + //Leave the event in the map so that it doesn't get recreated. The channel + //processing manager set the 'tuner not available' in the details already + P25ChannelGrantEvent event2 = mTS2ChannelGrantEventMap.get(rejectedFrequency); + if (event2 != null) { - event.setDetails(CHANNEL_START_REJECTED + " - " + event.getDetails()); - broadcast(event); + broadcast(event2); } }); break; } } + finally + { + mLock.unlock(); + } } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1CallSequenceRecorder.java b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1CallSequenceRecorder.java index dfd6b5ab8..1524822ab 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1CallSequenceRecorder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1CallSequenceRecorder.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -23,14 +23,14 @@ import io.github.dsheirer.audio.codec.mbe.MBECallSequence; import io.github.dsheirer.audio.codec.mbe.MBECallSequenceRecorder; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier; import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage; import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData; -import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUpdate; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUser; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdateExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUser; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectVoiceChannelUser; @@ -86,9 +86,9 @@ public void stop() @Override public void receive(IMessage message) { - if(message instanceof P25Message) + if(message instanceof P25P1Message) { - P25Message p25 = (P25Message)message; + P25P1Message p25 = (P25P1Message)message; if(p25.isValid()) { @@ -113,7 +113,7 @@ public void flush() /** * Processes any P25 Phase 1 message */ - public void process(P25Message message) + public void process(P25P1Message message) { if(message instanceof LDUMessage) { @@ -200,15 +200,15 @@ private void process(LinkControlWord lcw) mCallSequence.setToIdentifier(tivcu.getAddress().toString()); mCallSequence.setCallType(CALL_TYPE_TELEPHONE_INTERCONNECT); break; - case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER: - LCMotorolaPatchGroupVoiceChannelUser mpgvcu = (LCMotorolaPatchGroupVoiceChannelUser)lcw; + case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER: + LCMotorolaGroupRegroupVoiceChannelUser mpgvcu = (LCMotorolaGroupRegroupVoiceChannelUser)lcw; mCallSequence.setFromIdentifier(mpgvcu.getSourceAddress().toString()); - mCallSequence.setToIdentifier(mpgvcu.getGroupAddress().toString()); + mCallSequence.setToIdentifier(mpgvcu.getSupergroupAddress().toString()); mCallSequence.setCallType(CALL_TYPE_GROUP); break; - case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE: - LCMotorolaPatchGroupVoiceChannelUpdate mpgvcup = (LCMotorolaPatchGroupVoiceChannelUpdate)lcw; - mCallSequence.setToIdentifier(mpgvcup.getPatchGroup().toString()); + case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE: + LCMotorolaGroupRegroupVoiceChannelUpdate mpgvcup = (LCMotorolaGroupRegroupVoiceChannelUpdate)lcw; + mCallSequence.setToIdentifier(mpgvcup.getSupergroupAddress().toString()); mCallSequence.setCallType(CALL_TYPE_GROUP); break; case CALL_TERMINATION_OR_CANCELLATION: diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P2AudioModule.java b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P2AudioModule.java index 7abe7025b..c551a1d5a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P2AudioModule.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P2AudioModule.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -36,6 +36,8 @@ import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.IMessageProvider; import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PushToTalk; import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot; import io.github.dsheirer.preference.UserPreferences; @@ -111,10 +113,8 @@ public void receive(IMessage message) { if(message.getTimeslot() == getTimeslot()) { - if(message instanceof AbstractVoiceTimeslot) + if(message instanceof AbstractVoiceTimeslot abstractVoiceTimeslot) { - AbstractVoiceTimeslot abstractVoiceTimeslot = (AbstractVoiceTimeslot)message; - if(mEncryptedCallStateEstablished) { if(!mEncryptedCall) @@ -128,13 +128,18 @@ public void receive(IMessage message) mQueuedAudioTimeslots.offer(abstractVoiceTimeslot); } } - else if(message instanceof PushToTalk && message.isValid()) + else if(message instanceof MacMessage macMessage && message.isValid()) { - mEncryptedCallStateEstablished = true; - mEncryptedCall = ((PushToTalk)message).isEncrypted(); + MacStructure macStructure = macMessage.getMacStructure(); - //There should not be any pending voice timeslots to process since the PTT message is the first in - //the audio call sequence + if(macStructure instanceof PushToTalk pushToTalk) + { + mEncryptedCallStateEstablished = true; + mEncryptedCall = pushToTalk.isEncrypted(); + //There should not be any pending voice timeslots to process since the PTT message is the first in + //the audio call sequence. + clearPendingVoiceTimeslots(); + } } else if(message instanceof EncryptionSynchronizationSequence && message.isValid()) { @@ -159,6 +164,14 @@ private void processPendingVoiceTimeslots() } } + /** + * Clears/deletes any pending voice timeslots + */ + private void clearPendingVoiceTimeslots() + { + mQueuedAudioTimeslots.clear(); + } + /** * Process the audio voice frames * @param voiceFrames to process diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P2CallSequenceRecorder.java b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P2CallSequenceRecorder.java index c01cca254..708584b2e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P2CallSequenceRecorder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P2CallSequenceRecorder.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.audio; @@ -30,21 +27,20 @@ import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence; import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.EndPushToTalk; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PushToTalk; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelUser; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended; import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot; import io.github.dsheirer.preference.UserPreferences; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; - /** * P25 Phase 2 AMBE Frame recorder generates P25 call sequence recordings containing JSON representations of audio * frames, optional encryption and call identifiers. @@ -229,19 +225,19 @@ private void process(MacStructure mac, boolean isActive) case END_PUSH_TO_TALK: processEndPTT(mac); break; - case TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED: + case TDMA_01_GROUP_VOICE_CHANNEL_USER_ABBREVIATED: processGVCUA(mac); break; - case TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER: + case TDMA_02_UNIT_TO_UNIT_VOICE_CHANNEL_USER_ABBREVIATED: processUTUVCU(mac); break; - case TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: + case TDMA_03_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: processTIVCU(mac); break; - case TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED: + case TDMA_21_GROUP_VOICE_CHANNEL_USER_EXTENDED: processGVCUE(mac); break; - case TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: + case TDMA_22_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: processUTUVCUE(mac); break; } @@ -258,7 +254,7 @@ private void processUTUVCUE(MacStructure mac) { if(mac instanceof UnitToUnitVoiceChannelUserExtended) { UnitToUnitVoiceChannelUserExtended uuvcue = (UnitToUnitVoiceChannelUserExtended)mac; - mCallSequence.setFromIdentifier(uuvcue.getSourceAddress().toString()); + mCallSequence.setFromIdentifier(uuvcue.getSource().toString()); mCallSequence.setToIdentifier(uuvcue.getTargetAddress().toString()); mCallSequence.setCallType(CALL_TYPE_INDIVIDUAL); if(uuvcue.getServiceOptions().isEncrypted()) @@ -276,7 +272,7 @@ private void processGVCUE(MacStructure mac) { if(mac instanceof GroupVoiceChannelUserExtended) { GroupVoiceChannelUserExtended gvcue = (GroupVoiceChannelUserExtended)mac; - mCallSequence.setFromIdentifier(gvcue.getSourceAddress().toString()); + mCallSequence.setFromIdentifier(gvcue.getSource().toString()); mCallSequence.setToIdentifier(gvcue.getGroupAddress().toString()); mCallSequence.setCallType(CALL_TYPE_GROUP); if(gvcue.getServiceOptions().isEncrypted()) @@ -294,7 +290,7 @@ private void processTIVCU(MacStructure mac) { if(mac instanceof TelephoneInterconnectVoiceChannelUser) { TelephoneInterconnectVoiceChannelUser tivcu = (TelephoneInterconnectVoiceChannelUser)mac; - mCallSequence.setToIdentifier(tivcu.getToOrFromAddress().toString()); + mCallSequence.setToIdentifier(tivcu.getTargetAddress().toString()); mCallSequence.setCallType(CALL_TYPE_TELEPHONE_INTERCONNECT); if(tivcu.getServiceOptions().isEncrypted()) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Lra.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Lra.java index 4d42499ae..5016cf4e9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Lra.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Lra.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.identifier; @@ -46,4 +45,10 @@ public static Identifier create(int lra) { return new APCO25Lra(lra); } + + @Override + public String toString() + { + return getValue() + "/x" + String.format("%02X", getValue()); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Nac.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Nac.java index e52278445..0325728d3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Nac.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Nac.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.identifier; @@ -52,4 +51,10 @@ public static Identifier create(int nac) { return new APCO25Nac(nac); } + + @Override + public String toString() + { + return getValue() + "/x" + String.format("%03X", getValue()); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Rfss.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Rfss.java index 10d8f9a1c..d3dcce95a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Rfss.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Rfss.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.identifier; @@ -46,4 +45,10 @@ public static Identifier create(int rfss) { return new APCO25Rfss(rfss); } + + @Override + public String toString() + { + return getValue() + "/x" + String.format("%02X", getValue()); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Site.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Site.java index 72cd82684..6f8edca4a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Site.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Site.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,12 +14,11 @@ * * 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.identifier; import io.github.dsheirer.identifier.Form; -import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.IdentifierClass; import io.github.dsheirer.identifier.Role; import io.github.dsheirer.identifier.integer.IntegerIdentifier; @@ -42,8 +40,14 @@ public Protocol getProtocol() /** * Creates a new APCO-25 site identifier */ - public static Identifier create(int site) + public static IntegerIdentifier create(int site) { return new APCO25Site(site); } + + @Override + public String toString() + { + return getValue() + "/x" + String.format("%02X", getValue()); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25System.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25System.java index 6eb0d6d3d..82f23e401 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25System.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25System.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.identifier; @@ -45,4 +44,11 @@ public static Identifier create(int system) { return new APCO25System(system); } + + @Override + public String toString() + { + return getValue() + "/x" + String.format("%03X", getValue()); + } + } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Wacn.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Wacn.java index b65330f30..a6f7b7f78 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Wacn.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/APCO25Wacn.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.identifier; @@ -46,4 +45,11 @@ public static Identifier create(int wacn) { return new APCO25Wacn(wacn); } + + + @Override + public String toString() + { + return getValue() + "/x" + String.format("%05X", getValue()); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25Channel.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25Channel.java index 7407bf2c1..8661f14d9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25Channel.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/P25Channel.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.identifier.channel; @@ -25,7 +22,6 @@ import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; import io.github.dsheirer.protocol.Protocol; - import java.util.Objects; public class P25Channel implements IChannelDescriptor @@ -151,17 +147,24 @@ public boolean isTDMAChannel() } /** - * Timeslot for a TDMA channel - * @return timeslot or 0 if the channel is not a TDMA channel + * Timeslot for a TDMA channel, 1 or 2 + * + * Note: the ICD uses Logical Channel 0 (LCH 0) and Logical Channel 1 (LCH 1) but we refer to them as TS1 and TS2 + * so that timeslot 0 can be used for non timeslot signalling like Sync Loss messages. + * + * @return timeslot or 1 if the channel is not a TDMA channel */ public int getTimeslot() { if(isTDMAChannel()) { - return getDownlinkChannelNumber() % getTimeslotCount(); + if(getDownlinkChannelNumber() % getTimeslotCount() == 1) + { + return 2; + } } - return 0; + return 1; } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/StandardChannel.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/StandardChannel.java index 2848eeb7d..7c590c8b2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/StandardChannel.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/StandardChannel.java @@ -1,9 +1,27 @@ +/* + * ***************************************************************************** + * 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.identifier.channel; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; import io.github.dsheirer.protocol.Protocol; - import java.text.DecimalFormat; /** @@ -11,7 +29,7 @@ */ public class StandardChannel implements IChannelDescriptor { - private static final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat("0.000"); + private static final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat("0.0000"); private long mFrequency; /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/patch/APCO25PatchGroup.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/patch/APCO25PatchGroup.java index 95a1bdfeb..3dad9d5cb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/patch/APCO25PatchGroup.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/patch/APCO25PatchGroup.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,12 +14,13 @@ * * 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.identifier.patch; import io.github.dsheirer.identifier.patch.PatchGroup; import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.protocol.Protocol; /** @@ -43,4 +43,9 @@ public static APCO25PatchGroup create(PatchGroup patchGroup) { return new APCO25PatchGroup(patchGroup); } + + public static APCO25PatchGroup create(int supergroup) + { + return new APCO25PatchGroup(new PatchGroup(APCO25Talkgroup.create(supergroup))); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/radio/APCO25FullyQualifiedRadioIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/radio/APCO25FullyQualifiedRadioIdentifier.java index db2bbb9af..5c27b4fdf 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/radio/APCO25FullyQualifiedRadioIdentifier.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/radio/APCO25FullyQualifiedRadioIdentifier.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.identifier.radio; @@ -31,9 +28,17 @@ */ public class APCO25FullyQualifiedRadioIdentifier extends FullyQualifiedRadioIdentifier { - public APCO25FullyQualifiedRadioIdentifier(int wacn, int system, int id, Role role) + /** + * Constructs an instance + * @param localAddress radio identifier. This can be the same as the radio ID when the fully qualified radio + * is not being aliased on a local radio system. + * @param wacn of the home network for the radio. + * @param system of the home network for the radio. + * @param id of the radio within the home network. + */ + public APCO25FullyQualifiedRadioIdentifier(int localAddress, int wacn, int system, int id, Role role) { - super(wacn, system, id, role); + super(localAddress, wacn, system, id, role); } @Override @@ -42,13 +47,31 @@ public Protocol getProtocol() return Protocol.APCO25; } - public static APCO25FullyQualifiedRadioIdentifier createFrom(int wacn, int system, int id) + /** + * Creates a fully qualified radio and assigns the FROM role. + * @param localAddress radio identifier. This can be the same as the radio ID when the fully qualified radio + * is not being aliased on a local radio system. + * @param wacn of the home network for the radio. + * @param system of the home network for the radio. + * @param id of the radio within the home network. + * @return identifier + */ + public static APCO25FullyQualifiedRadioIdentifier createFrom(int localAddress, int wacn, int system, int id) { - return new APCO25FullyQualifiedRadioIdentifier(wacn, system, id, Role.FROM); + return new APCO25FullyQualifiedRadioIdentifier(localAddress, wacn, system, id, Role.FROM); } - public static APCO25FullyQualifiedRadioIdentifier createTo(int wacn, int system, int id) + /** + * Creates a fully qualified radio and assigns the TO role. + * @param localAddress radio identifier. This can be the same as the radio ID when the fully qualified radio + * is not being aliased on a local radio system. + * @param wacn of the home network for the radio. + * @param system of the home network for the radio. + * @param id of the radio within the home network. + * @return identifier + */ + public static APCO25FullyQualifiedRadioIdentifier createTo(int localAddress, int wacn, int system, int id) { - return new APCO25FullyQualifiedRadioIdentifier(wacn, system, id, Role.TO); + return new APCO25FullyQualifiedRadioIdentifier(localAddress, wacn, system, id, Role.TO); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FullyQualifiedTalkgroupIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FullyQualifiedTalkgroupIdentifier.java index 5cdab0cb3..c31a74c89 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FullyQualifiedTalkgroupIdentifier.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FullyQualifiedTalkgroupIdentifier.java @@ -1,29 +1,30 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.identifier.talkgroup; import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.protocol.Protocol; /** @@ -31,9 +32,17 @@ */ public class APCO25FullyQualifiedTalkgroupIdentifier extends FullyQualifiedTalkgroupIdentifier { - public APCO25FullyQualifiedTalkgroupIdentifier(int wacn, int system, int id, Role role) + /** + * Constructs an instance + * @param groupAddress used on the local system as an alias to the fully qualified talkgroup. + * @param wacn for the talkgroup home system. + * @param system for the talkgroup home system. + * @param id for the talkgroup within the home system. + * @param role played by the talkgroup. + */ + public APCO25FullyQualifiedTalkgroupIdentifier(int groupAddress, int wacn, int system, int id, Role role) { - super(wacn, system, id, role); + super(groupAddress, wacn, system, id, role); } @Override @@ -42,18 +51,39 @@ public Protocol getProtocol() return Protocol.APCO25; } - public static APCO25FullyQualifiedTalkgroupIdentifier createFrom(int wacn, int system, int id) + /** + * Creates an identifier for the fully qualified talkgroup ising the FROM role. + * @param groupAddress used on the local system as an alias to the fully qualified talkgroup. + * @param wacn for the talkgroup home system. + * @param system for the talkgroup home system. + * @param id for the talkgroup within the home system. + */ + public static APCO25FullyQualifiedTalkgroupIdentifier createFrom(int groupAddress, int wacn, int system, int id) { - return new APCO25FullyQualifiedTalkgroupIdentifier(wacn, system, id, Role.FROM); + return new APCO25FullyQualifiedTalkgroupIdentifier(groupAddress, wacn, system, id, Role.FROM); } - public static APCO25FullyQualifiedTalkgroupIdentifier createTo(int wacn, int system, int id) + /** + * Creates an identifier for the fully qualified talkgroup ising the TO role. + * @param groupAddress used on the local system as an alias to the fully qualified talkgroup. + * @param wacn for the talkgroup home system. + * @param system for the talkgroup home system. + * @param id for the talkgroup within the home system. + */ + public static APCO25FullyQualifiedTalkgroupIdentifier createTo(int groupAddress, int wacn, int system, int id) { - return new APCO25FullyQualifiedTalkgroupIdentifier(wacn, system, id, Role.TO); + return new APCO25FullyQualifiedTalkgroupIdentifier(groupAddress, wacn, system, id, Role.TO); } - public static APCO25FullyQualifiedTalkgroupIdentifier createAny(int wacn, int system, int id) + /** + * Creates an identifier for the fully qualified talkgroup ising the ANY role. + * @param groupAddress used on the local system as an alias to the fully qualified talkgroup. + * @param wacn for the talkgroup home system. + * @param system for the talkgroup home system. + * @param id for the talkgroup within the home system. + */ + public static APCO25FullyQualifiedTalkgroupIdentifier createAny(int groupAddress, int wacn, int system, int id) { - return new APCO25FullyQualifiedTalkgroupIdentifier(wacn, system, id, Role.ANY); + return new APCO25FullyQualifiedTalkgroupIdentifier(groupAddress, wacn, system, id, Role.ANY); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/DecodeConfigP25.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/DecodeConfigP25.java new file mode 100644 index 000000000..bab3c8be6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/DecodeConfigP25.java @@ -0,0 +1,72 @@ +/* + * ***************************************************************************** + * 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; + + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import io.github.dsheirer.module.decode.config.DecodeConfiguration; +import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = DecodeConfigP25Phase1.class, name = "decodeConfigP25Phase1"), + @JsonSubTypes.Type(value = DecodeConfigP25Phase2.class, name = "decodeConfigP25Phase2"), +}) +public abstract class DecodeConfigP25 extends DecodeConfiguration +{ + private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT; + private boolean mIgnoreDataCalls = false; + + public DecodeConfigP25() + { + } + + @JacksonXmlProperty(isAttribute = true, localName = "ignore_data_calls") + public boolean getIgnoreDataCalls() + { + return mIgnoreDataCalls; + } + + public void setIgnoreDataCalls(boolean ignore) + { + mIgnoreDataCalls = ignore; + } + + + @JacksonXmlProperty(isAttribute = true, localName = "traffic_channel_pool_size") + public int getTrafficChannelPoolSize() + { + return mTrafficChannelPoolSize; + } + + /** + * Sets the traffic channel pool size which is the maximum number of + * simultaneous traffic channels that can be allocated. + * + * This limits the maximum calls so that busy systems won't cause more + * traffic channels to be allocated than the decoder/software/host computer + * can support. + */ + public void setTrafficChannelPoolSize(int size) + { + mTrafficChannelPoolSize = size; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/DecodeConfigP25Phase1.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/DecodeConfigP25Phase1.java index bf94144ae..2ec64b075 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/DecodeConfigP25Phase1.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/DecodeConfigP25Phase1.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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; @@ -25,10 +22,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.source.tuner.channel.ChannelSpecification; -public class DecodeConfigP25Phase1 extends DecodeConfiguration +/** + * APCO25 Phase 1 decoder configuration + */ +public class DecodeConfigP25Phase1 extends DecodeConfigP25 { public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 400; public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500; @@ -36,9 +35,9 @@ public class DecodeConfigP25Phase1 extends DecodeConfiguration private P25P1Decoder.Modulation mModulation = P25P1Decoder.Modulation.C4FM; - private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT; - private boolean mIgnoreDataCalls = false; - + /** + * Constructs an instance + */ public DecodeConfigP25Phase1() { } @@ -60,37 +59,6 @@ public void setModulation(P25P1Decoder.Modulation modulation) mModulation = modulation; } - @JacksonXmlProperty(isAttribute = true, localName = "ignore_data_calls") - public boolean getIgnoreDataCalls() - { - return mIgnoreDataCalls; - } - - public void setIgnoreDataCalls(boolean ignore) - { - mIgnoreDataCalls = ignore; - } - - - @JacksonXmlProperty(isAttribute = true, localName = "traffic_channel_pool_size") - public int getTrafficChannelPoolSize() - { - return mTrafficChannelPoolSize; - } - - /** - * Sets the traffic channel pool size which is the maximum number of - * simultaneous traffic channels that can be allocated. - * - * This limits the maximum calls so that busy systems won't cause more - * traffic channels to be allocated than the decoder/software/host computer - * can support. - */ - public void setTrafficChannelPoolSize(int size) - { - mTrafficChannelPoolSize = size; - } - /** * Source channel specification for this decoder */ diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java index a27ac7981..865facec9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1Decoder.java @@ -18,11 +18,16 @@ */ package io.github.dsheirer.module.decode.p25.phase1; +import com.google.common.eventbus.Subscribe; import io.github.dsheirer.dsp.squelch.PowerMonitor; import io.github.dsheirer.dsp.symbol.Dibit; import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.FeedbackDecoder; +import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.IByteBufferProvider; @@ -52,6 +57,18 @@ public P25P1Decoder(double symbolRate) getDibitBroadcaster().addListener(mByteBufferAssembler); } + /** + * Preloads P25 frequency bands when this is a traffic channel, since they are not transmitted on the traffic + * channel and the decoder state needs accurate frequency values to pass to the traffic channel manager for + * call event tracking. + * @param preLoadDataContent with known frequency bands. + */ + @Subscribe + public void process(P25FrequencyBandPreloadDataContent preLoadDataContent) + { + mMessageProcessor.preload(preLoadDataContent); + } + @Override public void setSourceEventListener(Listener listener ) { 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 5a53fd279..f7a81b161 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 @@ -31,12 +31,14 @@ import io.github.dsheirer.identifier.Form; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.IdentifierClass; -import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.identifier.MutableIdentifierCollection; import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier; +import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier; import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; import io.github.dsheirer.identifier.patch.PatchGroupManager; import io.github.dsheirer.identifier.patch.PatchGroupPreLoadDataContent; +import io.github.dsheirer.log.LoggingSuppressor; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.DecodeEvent; @@ -53,36 +55,53 @@ import io.github.dsheirer.module.decode.p25.P25DecodeEvent; import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage; import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlOpcode; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisReturnToControlChannel; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaEmergencyAlarmActivation; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkComplete; import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnitGPS; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCCallTermination; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCExtendedFunctionCommand; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCExtendedFunctionCommandExtended; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdateExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCMessageUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCMessageUpdateExtended; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCNetworkStatusBroadcast; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCNetworkStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCRFSSStatusBroadcast; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCRFSSStatusBroadcastExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusUpdateExtended; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectAnswerRequest; import io.github.dsheirer.module.decode.p25.phase1.message.ldu.EncryptionSyncParameters; import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU1Message; import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU2Message; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequenceMessage; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCAuthenticationResponse; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCIndividualDataServiceRequest; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCLocationRegistrationRequest; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCMessageUpdateRequest; -import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCStatusQueryResponse; -import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCStatusUpdateRequest; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitAcknowledgeResponse; -import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitToUnitVoiceServiceAnswerResponse; +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.AMBTCGroupAffiliationResponse; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCGroupDataChannelGrant; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCGroupVoiceChannelGrant; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCIndividualDataChannelGrant; 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; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCTelephoneInterconnectChannelGrantUpdate; @@ -91,14 +110,18 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrantUpdate; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.pdu.response.ResponseMessage; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.isp.UMBTCTelephoneInterconnectRequestExplicitDialing; import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.HarrisGroupRegroupExplicitEncryptionCommand; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.L3HarrisGroupRegroupExplicitEncryptionCommand; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaAcknowledgeResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaDenyResponse; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExtendedFunctionCommand; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelGrant; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaQueuedResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.CancelServiceRequest; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.ExtendedFunctionResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.GroupAffiliationQueryResponse; @@ -110,6 +133,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.SNDCPDataChannelRequest; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.SNDCPDataPageResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.SNDCPReconnectRequest; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.StatusQueryRequest; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.StatusQueryResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.StatusUpdateRequest; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.TelephoneInterconnectAnswerResponse; @@ -127,9 +151,12 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.GroupVoiceChannelGrantUpdateExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.LocationRegistrationResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.MessageUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.NetworkStatusBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.QueuedResponse; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.RFSSStatusBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.RoamingAddressCommand; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SNDCPDataChannelGrant; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.StatusQuery; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.StatusUpdate; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.TelephoneInterconnectAnswerRequest; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.TelephoneInterconnectVoiceChannelGrant; @@ -137,11 +164,12 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.UnitRegistrationResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.UnitToUnitVoiceChannelGrant; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.UnitToUnitVoiceChannelGrantUpdate; -import io.github.dsheirer.module.decode.p25.reference.Encryption; import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.util.PacketUtil; +import java.util.Collections; import java.util.List; import org.jdesktop.swingx.mapviewer.GeoPosition; import org.slf4j.Logger; @@ -150,19 +178,17 @@ /** * Decoder state for an APCO25 channel. Maintains the call/data/idle state of the channel and produces events by * monitoring the decoded message stream. - * */ public class P25P1DecoderState extends DecoderState implements IChannelEventListener { - private final static Logger mLog = LoggerFactory.getLogger(P25P1DecoderState.class); - - private ChannelType mChannelType; - private P25P1Decoder.Modulation mModulation; - private PatchGroupManager mPatchGroupManager = new PatchGroupManager(); - private P25P1NetworkConfigurationMonitor mNetworkConfigurationMonitor; + private static final Logger mLog = LoggerFactory.getLogger(P25P1DecoderState.class); + private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(mLog); + private final Channel mChannel; + private final P25P1Decoder.Modulation mModulation; + private final PatchGroupManager mPatchGroupManager = new PatchGroupManager(); + private final P25P1NetworkConfigurationMonitor mNetworkConfigurationMonitor; + private final Listener mChannelEventListener; private P25TrafficChannelManager mTrafficChannelManager; - private Listener mChannelEventListener; - private DecodeEvent mCurrentCallEvent; /** * Constructs an APCO-25 decoder state with an optional traffic channel manager. @@ -171,7 +197,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList */ public P25P1DecoderState(Channel channel, P25TrafficChannelManager trafficChannelManager) { - mChannelType = channel.getChannelType(); + mChannel = channel; mModulation = ((DecodeConfigP25Phase1)channel.getDecodeConfiguration()).getModulation(); mNetworkConfigurationMonitor = new P25P1NetworkConfigurationMonitor(mModulation); @@ -182,9 +208,9 @@ public P25P1DecoderState(Channel channel, P25TrafficChannelManager trafficChanne } else { - mChannelEventListener = channelEvent -> { - //do nothing with channel events if we're not configured to process traffic channels - }; + mTrafficChannelManager = new P25TrafficChannelManager(channel); + //Do nothing with channel events if we're not configured to process traffic channels + mChannelEventListener = channelEvent -> {}; } } @@ -224,31 +250,6 @@ public Listener getChannelEventListener() return mChannelEventListener; } - /** - * Performs a full reset to prepare this object for reuse on a new channel - */ - @Override - public void reset() - { - super.reset(); - resetState(); - } - - /** - * Resets any temporal state details - */ - protected void resetState() - { - super.resetState(); - - if(mCurrentCallEvent != null) - { - mCurrentCallEvent.end(System.currentTimeMillis()); - broadcast(mCurrentCallEvent); - mCurrentCallEvent = null; - } - } - /** * Processes an identifier collection to harvest Patch Groups to preload when this channel is first starting up. * @param preLoadDataContent containing an identifier collection with optional patch group identifier(s). @@ -260,7 +261,7 @@ public void process(PatchGroupPreLoadDataContent preLoadDataContent) { if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier) { - mPatchGroupManager.addPatchGroup(patchGroupIdentifier); + mPatchGroupManager.addPatchGroup(patchGroupIdentifier, preLoadDataContent.getTimestamp()); } } } @@ -271,10 +272,8 @@ public void process(PatchGroupPreLoadDataContent preLoadDataContent) @Override public void receive(IMessage iMessage) { - if(iMessage instanceof P25Message) + if(iMessage instanceof P25P1Message message) { - P25Message message = (P25Message)iMessage; - getIdentifierCollection().update(message.getNAC()); switch(message.getDUID()) @@ -283,7 +282,7 @@ public void receive(IMessage iMessage) processAMBTC(message); break; case HEADER_DATA_UNIT: - processHDU((HDUMessage)message); + processHDU(message); break; case IP_PACKET_DATA: processPacketData(message); @@ -319,22 +318,146 @@ public void receive(IMessage iMessage) } /** - * Commands the traffic channel manager to process a traffic channel grant and allocate a decoder - * to process the traffic channel. + * Commands the traffic channel manager to process a traffic channel grant and allocate a decoder to process the + * traffic channel. * @param apco25Channel to allocate * @param serviceOptions for the channel - * @param identifierCollection identifying the users of the channel + * @param identifiers to add to the current identifier collection * @param opcode that identifies the type of channel grant * @param timestamp when the channel grant occurred. */ private void processChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, - IdentifierCollection identifierCollection, Opcode opcode, long timestamp) + List identifiers, Opcode opcode, long timestamp) + { + if(apco25Channel.getValue().getDownlinkFrequency() > 0) + { + MutableIdentifierCollection mic = getMutableIdentifierCollection(identifiers, timestamp); + mTrafficChannelManager.processP1ChannelGrant(apco25Channel, serviceOptions, mic, opcode, timestamp); + } + } + + /** + * Process an update for another channel and send it to the traffic channel manager. + * @param channel where the call activity is happening. + * @param serviceOptions for the call, optional null. + * @param identifiers involved in the call + * @param opcode for the update + * @param timestamp of the message + */ + private void processChannelUpdate(APCO25Channel channel, ServiceOptions serviceOptions, List identifiers, + Opcode opcode, long timestamp) + { + MutableIdentifierCollection mic = getMutableIdentifierCollection(identifiers, timestamp); + mTrafficChannelManager.processP1ChannelUpdate(channel, serviceOptions, mic, opcode, timestamp); + } + + /** + * Creates a decode event type from the link control word + */ + private DecodeEventType getLCDecodeEventType(LinkControlWord lcw) + { + boolean encrypted = lcw.isEncrypted();; + + switch(lcw.getOpcode()) + { + case GROUP_VOICE_CHANNEL_USER: + return encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP; + case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER: + return encrypted ? DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED : DecodeEventType.CALL_PATCH_GROUP; + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: + return encrypted ? DecodeEventType.CALL_INTERCONNECT_ENCRYPTED : DecodeEventType.CALL_INTERCONNECT; + case UNIT_TO_UNIT_VOICE_CHANNEL_USER: + case UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: + return encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT; + default: + return encrypted ? DecodeEventType.CALL_ENCRYPTED : DecodeEventType.CALL; + } + } + + /** + * Link Control (LC) Channel user (ie current user on this channel). + */ + private void processLCChannelUser(LinkControlWord lcw, long timestamp) + { + List updated = mPatchGroupManager.update(lcw.getIdentifiers(), timestamp); + getIdentifierCollection().update(updated); + DecodeEventType decodeEventType = getLCDecodeEventType(lcw); + ServiceOptions serviceOptions = lcw.isEncrypted() ? VoiceServiceOptions.createEncrypted() : + VoiceServiceOptions.createUnencrypted(); + mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), getCurrentChannel(), decodeEventType, + serviceOptions, getIdentifierCollection(), timestamp, null ); + } + + /** + * Broadcasts an event from the AMBTC message + * @param ambtcMessage with identifiers + * @param decodeEventType for the event + * @param details to add to the event + */ + private void broadcastEvent(AMBTCMessage ambtcMessage, DecodeEventType decodeEventType, String details) + { + broadcastEvent(ambtcMessage.getIdentifiers(), ambtcMessage.getTimestamp(), decodeEventType, details); + } + + /** + * Broadcasts an event from the TSBK message + * @param tsbkMessage with identifiers + * @param decodeEventType for the event + * @param details to add to the event + */ + private void broadcastEvent(TSBKMessage tsbkMessage, DecodeEventType decodeEventType, String details) + { + broadcastEvent(tsbkMessage.getIdentifiers(), tsbkMessage.getTimestamp(), decodeEventType, details); + } + + /** + * Broadcasts the arguments as a new decode event + * @param identifiers involved in the event + * @param timestamp of the message/event + * @param decodeEventType of the event + * @param details for the event + */ + private void broadcastEvent(List identifiers, long timestamp, DecodeEventType decodeEventType, String details) + { + MutableIdentifierCollection mic = getMutableIdentifierCollection(identifiers, timestamp); + + broadcast(P25DecodeEvent.builder(decodeEventType, timestamp) + .channel(getCurrentChannel()) + .details(details) + .identifiers(mic) + .build()); + } + + /** + * Creates a copy of the current identifier collection, removes any USER identifiers and adds the argument identifiers + * passing each identifier through the patch group manager to replace with a patch group if it exists + * @param identifiers to add to the collection copy + * @param timestamp to check for freshness of patch group info. + * @return collection + */ + private MutableIdentifierCollection getMutableIdentifierCollection(List identifiers, long timestamp) { - if(mTrafficChannelManager != null && apco25Channel.getValue().getFrequencyBand() != null) + MutableIdentifierCollection requestCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + requestCollection.remove(IdentifierClass.USER); + + for(Identifier identifier: identifiers) { - mTrafficChannelManager.processChannelGrant(apco25Channel, serviceOptions, identifierCollection, opcode, - timestamp); + requestCollection.update(mPatchGroupManager.update(identifier, timestamp)); } + + return requestCollection; + } + + /** + * Creates a copy of the current identifier collection, removes any USER identifiers and adds the argument identifier + * passed through the patch group manager to replace with a patch group if it exists + * @param identifier to add to the collection copy + * @param timestamp to check for freshness of patch group info. + * @return collection + */ + private MutableIdentifierCollection getMutableIdentifierCollection(Identifier identifier, long timestamp) + { + return getMutableIdentifierCollection(Collections.singletonList(identifier), timestamp); } /** @@ -342,77 +465,77 @@ private void processChannelGrant(APCO25Channel apco25Channel, ServiceOptions ser * * @param message */ - private void processAMBTC(P25Message message) + private void processAMBTC(P25P1Message message) { - if(message instanceof AMBTCMessage && message.isValid()) + if(message.isValid() && message instanceof AMBTCMessage ambtc) { - AMBTCMessage ambtc = (AMBTCMessage)message; - switch(ambtc.getHeader().getOpcode()) { case ISP_AUTHENTICATION_RESPONSE: - processAMBTCIspAuthenticationResponse(message, ambtc); + if(ambtc instanceof AMBTCAuthenticationResponse ar) + { + broadcastEvent(ambtc, DecodeEventType.RESPONSE, "AUTHENTICATION:" + + ar.getAuthenticationValue()); + } break; case ISP_CALL_ALERT_REQUEST: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, "CALL ALERT"); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, + "CALL ALERT"); break; case ISP_GROUP_AFFILIATION_REQUEST: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, "GROUP AFFILIATION"); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, + "GROUP AFFILIATION"); break; case ISP_INDIVIDUAL_DATA_SERVICE_REQUEST: - if(ambtc instanceof AMBTCIndividualDataServiceRequest) + if(ambtc instanceof AMBTCIndividualDataServiceRequest idsr) { - AMBTCIndividualDataServiceRequest idsr = (AMBTCIndividualDataServiceRequest)ambtc; - - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, "INDIVIDUAL DATA SERVICE " + idsr.getDataServiceOptions()); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, + "INDIVIDUAL DATA SERVICE " + idsr.getDataServiceOptions()); } break; case ISP_LOCATION_REGISTRATION_REQUEST: - if(ambtc instanceof AMBTCLocationRegistrationRequest) + if(ambtc instanceof AMBTCLocationRegistrationRequest lrr) { - AMBTCLocationRegistrationRequest lrr = (AMBTCLocationRegistrationRequest)ambtc; - - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, "LOCATION REGISTRATION - UNIQUE ID:" + lrr.getSourceId()); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, + "LOCATION REGISTRATION - UNIQUE ID:" + lrr.getSourceId()); } break; case ISP_MESSAGE_UPDATE_REQUEST: - if(ambtc instanceof AMBTCMessageUpdateRequest) + if(ambtc instanceof AMBTCMessageUpdateRequest mur) { - AMBTCMessageUpdateRequest mur = (AMBTCMessageUpdateRequest)ambtc; - - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.SDM, "MESSAGE:" + mur.getShortDataMessage()); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.SDM, + "MESSAGE:" + mur.getShortDataMessage()); } break; case ISP_ROAMING_ADDRESS_REQUEST: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, "ROAMING ADDRESS"); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, + "ROAMING ADDRESS"); break; case ISP_STATUS_QUERY_REQUEST: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, "STATUS QUERY"); - break; case ISP_STATUS_QUERY_RESPONSE: - processAMBTCStatusQueryResponse(ambtc); - break; case ISP_STATUS_UPDATE_REQUEST: - processAMBTCStatusUpdateRequest(ambtc); + processAMBTCStatus(ambtc); break; case ISP_UNIT_ACKNOWLEDGE_RESPONSE: - if(ambtc instanceof AMBTCUnitAcknowledgeResponse) + if(ambtc instanceof AMBTCUnitAcknowledgeResponse uar) { - AMBTCUnitAcknowledgeResponse uar = (AMBTCUnitAcknowledgeResponse)ambtc; - - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.RESPONSE, "ACKNOWLEDGE:" + uar.getAcknowledgedService()); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.RESPONSE, + "ACKNOWLEDGE:" + uar.getAcknowledgedService()); } break; case ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST: - if(ambtc instanceof AMBTCUnitToUnitVoiceServiceRequest) + if(ambtc instanceof AMBTCUnitToUnitVoiceServiceRequest uuvsr) { - AMBTCUnitToUnitVoiceServiceRequest uuvsr = (AMBTCUnitToUnitVoiceServiceRequest)ambtc; - - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, "UNIT-2-UNIT VOICE SERVICE " + uuvsr.getVoiceServiceOptions()); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, + "UNIT-2-UNIT VOICE SERVICE " + uuvsr.getVoiceServiceOptions()); } break; case ISP_UNIT_TO_UNIT_ANSWER_RESPONSE: - processAMBTCUnitToUnitAnswerResponse(ambtc); + if(ambtc instanceof AMBTCUnitToUnitAnswerResponse uuar) + { + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, + "UNIT-2-UNIT ANSWER RESPONSE " + uuar.getAnswerResponse()); + } break; //Network configuration messages @@ -420,75 +543,107 @@ private void processAMBTC(P25Message message) mNetworkConfigurationMonitor.process(ambtc); break; case OSP_NETWORK_STATUS_BROADCAST: + if((getCurrentChannel() == null || getCurrentChannel().getDownlinkFrequency() > 0) && + mChannel.isStandardChannel() && ambtc instanceof AMBTCNetworkStatusBroadcast nsb && + nsb.getChannel().getDownlinkFrequency() > 0) + { + setCurrentChannel(nsb.getChannel()); + DecoderLogicalChannelNameIdentifier channelID = + DecoderLogicalChannelNameIdentifier.create(nsb.getChannel().toString(), Protocol.APCO25); + getIdentifierCollection().update(channelID); + setCurrentFrequency(nsb.getChannel().getDownlinkFrequency()); + FrequencyConfigurationIdentifier frequencyID = FrequencyConfigurationIdentifier + .create(nsb.getChannel().getDownlinkFrequency()); + getIdentifierCollection().update(frequencyID); + + } mNetworkConfigurationMonitor.process(ambtc); break; case OSP_RFSS_STATUS_BROADCAST: + if((getCurrentChannel() == null || getCurrentChannel().getDownlinkFrequency() > 0) && + mChannel.isStandardChannel() && ambtc instanceof AMBTCRFSSStatusBroadcast rsb && + rsb.getChannel().getDownlinkFrequency() > 0) + { + setCurrentChannel(rsb.getChannel()); + DecoderLogicalChannelNameIdentifier channelID = + DecoderLogicalChannelNameIdentifier.create(rsb.getChannel().toString(), Protocol.APCO25); + getIdentifierCollection().update(channelID); + setCurrentFrequency(rsb.getChannel().getDownlinkFrequency()); + FrequencyConfigurationIdentifier frequencyID = FrequencyConfigurationIdentifier + .create(rsb.getChannel().getDownlinkFrequency()); + getIdentifierCollection().update(frequencyID); + + } mNetworkConfigurationMonitor.process(ambtc); break; //Channel grants case OSP_GROUP_DATA_CHANNEL_GRANT: - processAMBTCGroupDataChannelGrant(ambtc); - break; case OSP_GROUP_VOICE_CHANNEL_GRANT: - processAMBTCGroupVoiceChannelGrant(ambtc); - break; case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: - processAMBTCIndividualDataChannelGrant(ambtc); - break; case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - processAMBTCTelephoneInterconnectVoiceChannelGrant(ambtc); - break; - case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: - processAMBTCTelephoneInterconnectVoiceChannelGrantUpdate(ambtc); - break; case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - processAMBTCUnitToUnitVoiceChannelGrant(ambtc); + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT: + processAMBTCChannelGrant(ambtc); break; + + //Channel grant updates + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: - processAMBTCUnitToUnitVoiceChannelGrantUpdate(ambtc); + processAMBTCChannelGrantUpdate(ambtc); break; + case OSP_UNIT_TO_UNIT_ANSWER_REQUEST: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.PAGE, "ANSWER REQUEST"); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.PAGE, + "ANSWER REQUEST"); break; case OSP_CALL_ALERT: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.PAGE, "CALL ALERT"); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.PAGE, + "CALL ALERT"); break; case OSP_GROUP_AFFILIATION_QUERY: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.QUERY, "GROUP AFFILIATION"); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.QUERY, + "GROUP AFFILIATION"); break; case OSP_GROUP_AFFILIATION_RESPONSE: - processAMBTCGroupAffiliationResponse(ambtc); + if(ambtc instanceof AMBTCGroupAffiliationResponse gar) + { + broadcastEvent(ambtc, DecodeEventType.RESPONSE, "AFFILIATION GROUP:" + + gar.getGroupAddress() + " ANNOUNCEMENT GROUP:" + gar.getAnnouncementGroup()); + } break; case OSP_MESSAGE_UPDATE: - if(ambtc instanceof AMBTCMessageUpdate) + if(ambtc instanceof AMBTCMessageUpdate mu) { - AMBTCMessageUpdate mu = (AMBTCMessageUpdate)ambtc; - - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.SDM, "MESSAGE:" + mu.getShortDataMessage()); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.SDM, "MESSAGE:" + + mu.getShortDataMessage()); } break; - case OSP_PROTECTION_PARAMETER_BROADCAST: - processAMBTCProtectionParameterBroadcast(ambtc); + 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: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.RESPONSE, "ROAMING ADDRESS UPDATE"); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.RESPONSE, + "ROAMING ADDRESS UPDATE"); break; case OSP_ROAMING_ADDRESS_COMMAND: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.COMMAND, "ROAMING ADDRESS"); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.COMMAND, + "ROAMING ADDRESS"); break; case OSP_STATUS_QUERY: - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.QUERY, "STATUS"); - break; case OSP_STATUS_UPDATE: - processAMBTCStatusUpdate(ambtc); + processAMBTCStatus(ambtc); break; case OSP_UNIT_REGISTRATION_RESPONSE: - if(ambtc instanceof AMBTCUnitRegistrationResponse) + if(ambtc instanceof AMBTCUnitRegistrationResponse urr) { - AMBTCUnitRegistrationResponse urr = (AMBTCUnitRegistrationResponse)ambtc; - - processBroadcast(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REGISTER, urr.getResponse() + " UNIT REGISTRATION"); + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REGISTER, + urr.getResponse() + " UNIT REGISTRATION"); } break; default: @@ -500,211 +655,130 @@ private void processAMBTC(P25Message message) broadcast(new DecoderStateEvent(this, Event.DECODE, State.CONTROL)); } - private void processAMBTCStatusUpdate(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCStatusUpdate) - { - AMBTCStatusUpdate su = (AMBTCStatusUpdate)ambtc; - processBroadcast(ambtc, DecodeEventType.STATUS, - "UNIT:" + su.getUnitStatus() + " USER:" + su.getUserStatus()); - } - } - - private void processAMBTCProtectionParameterBroadcast(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCProtectionParameterBroadcast) - { - AMBTCProtectionParameterBroadcast ppb = (AMBTCProtectionParameterBroadcast)ambtc; - processBroadcast(ambtc, DecodeEventType.RESPONSE, "USE ENCRYPTION " + ppb.getEncryptionKey() + - " OUTBOUND MI:" + ppb.getOutboundMessageIndicator() + - " INBOUND MI:" + ppb.getInboundMessageIndicator()); - } - } - - private void processAMBTCGroupAffiliationResponse(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCGroupAffiliationResponse) - { - AMBTCGroupAffiliationResponse gar = (AMBTCGroupAffiliationResponse)ambtc; - processBroadcast(ambtc, DecodeEventType.RESPONSE, "AFFILIATION GROUP:" + gar.getGroupId() + - " ANNOUNCEMENT GROUP:" + gar.getAnnouncementGroupId()); - } - } - - private void processAMBTCUnitToUnitVoiceChannelGrantUpdate(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCUnitToUnitVoiceServiceChannelGrantUpdate) - { - AMBTCUnitToUnitVoiceServiceChannelGrantUpdate uuvscgu = (AMBTCUnitToUnitVoiceServiceChannelGrantUpdate)ambtc; - - MutableIdentifierCollection identifierCollection = getMutableIdentifierCollection(uuvscgu.getIdentifiers()); - - processChannelGrant(uuvscgu.getChannel(), uuvscgu.getVoiceServiceOptions(), - identifierCollection, ambtc.getHeader().getOpcode(), - ambtc.getTimestamp()); - } - } - - private void processAMBTCUnitToUnitVoiceChannelGrant(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCUnitToUnitVoiceServiceChannelGrant) - { - AMBTCUnitToUnitVoiceServiceChannelGrant uuvscg = (AMBTCUnitToUnitVoiceServiceChannelGrant)ambtc; - - MutableIdentifierCollection identifierCollection = getMutableIdentifierCollection(uuvscg.getIdentifiers()); - - processChannelGrant(uuvscg.getChannel(), uuvscg.getVoiceServiceOptions(), - identifierCollection, ambtc.getHeader().getOpcode(), - ambtc.getTimestamp()); - } - } - - private void processAMBTCTelephoneInterconnectVoiceChannelGrantUpdate(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCTelephoneInterconnectChannelGrantUpdate) - { - AMBTCTelephoneInterconnectChannelGrantUpdate ticgu = (AMBTCTelephoneInterconnectChannelGrantUpdate)ambtc; - - MutableIdentifierCollection identifierCollection = getMutableIdentifierCollection(ticgu.getIdentifiers()); - - processChannelGrant(ticgu.getChannel(), ticgu.getVoiceServiceOptions(), - identifierCollection, ambtc.getHeader().getOpcode(), - ambtc.getTimestamp()); - } - } - - private void processAMBTCTelephoneInterconnectVoiceChannelGrant(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCTelephoneInterconnectChannelGrant) - { - AMBTCTelephoneInterconnectChannelGrant ticg = (AMBTCTelephoneInterconnectChannelGrant)ambtc; - - MutableIdentifierCollection identifierCollection = getMutableIdentifierCollection(ticg.getIdentifiers()); - - processChannelGrant(ticg.getChannel(), ticg.getVoiceServiceOptions(), - identifierCollection, ambtc.getHeader().getOpcode(), - ambtc.getTimestamp()); - } - } - - private void processAMBTCIndividualDataChannelGrant(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCIndividualDataChannelGrant) - { - AMBTCIndividualDataChannelGrant idcg = (AMBTCIndividualDataChannelGrant)ambtc; - - MutableIdentifierCollection identifierCollection = getMutableIdentifierCollection(idcg.getIdentifiers()); - - processChannelGrant(idcg.getChannel(), idcg.getDataServiceOptions(), - identifierCollection, ambtc.getHeader().getOpcode(), - ambtc.getTimestamp()); - } - } - - private void processAMBTCGroupVoiceChannelGrant(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCGroupVoiceChannelGrant) - { - AMBTCGroupVoiceChannelGrant gvcg = (AMBTCGroupVoiceChannelGrant)ambtc; - - MutableIdentifierCollection identifierCollection = getMutableIdentifierCollection(gvcg.getIdentifiers()); - - processChannelGrant(gvcg.getChannel(), gvcg.getVoiceServiceOptions(), - identifierCollection, ambtc.getHeader().getOpcode(), - ambtc.getTimestamp()); - } - } - - private void processAMBTCGroupDataChannelGrant(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCGroupDataChannelGrant) - { - AMBTCGroupDataChannelGrant gdcg = (AMBTCGroupDataChannelGrant)ambtc; - - MutableIdentifierCollection identifierCollection = getMutableIdentifierCollection(gdcg.getIdentifiers()); - processChannelGrant(gdcg.getChannel(), gdcg.getDataServiceOptions(), - identifierCollection, ambtc.getHeader().getOpcode(), - ambtc.getTimestamp()); - } - } - - private void processAMBTCUnitToUnitAnswerResponse(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCUnitToUnitVoiceServiceAnswerResponse) - { - AMBTCUnitToUnitVoiceServiceAnswerResponse uuvsar = (AMBTCUnitToUnitVoiceServiceAnswerResponse)ambtc; - processBroadcast(ambtc, DecodeEventType.RESPONSE, - uuvsar.getAnswerResponse() + " UNIT-2-UNIT VOICE SERVICE " + uuvsar.getVoiceServiceOptions()); - } - } - - private void processAMBTCStatusUpdateRequest(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCStatusUpdateRequest) + /** + * Process AMBTC status message + */ + private void processAMBTCStatus(AMBTCMessage ambtc) + { + switch(ambtc.getHeader().getOpcode()) { - AMBTCStatusUpdateRequest sur = (AMBTCStatusUpdateRequest)ambtc; - processBroadcast(ambtc, DecodeEventType.STATUS, - "UNIT:" + sur.getUnitStatus() + " USER:" + sur.getUserStatus()); + case ISP_STATUS_QUERY_REQUEST: + case OSP_STATUS_QUERY: + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, + "STATUS QUERY"); + break; + case ISP_STATUS_QUERY_RESPONSE: + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.RESPONSE, + "STATUS QUERY"); + break; + case ISP_STATUS_UPDATE_REQUEST: + broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.REQUEST, + "STATUS UPDATE"); + break; + case OSP_STATUS_UPDATE: + if(ambtc instanceof AMBTCStatusUpdate su) + { + broadcastEvent(ambtc, DecodeEventType.STATUS, "UNIT:" + su.getUnitStatus() + " USER:" + + su.getUserStatus()); + } + break; } } - private void processAMBTCStatusQueryResponse(AMBTCMessage ambtc) { - if(ambtc instanceof AMBTCStatusQueryResponse) + /** + * AMBTC Channel Grant Updates + */ + private void processAMBTCChannelGrantUpdate(AMBTCMessage ambtc) + { + switch(ambtc.getHeader().getOpcode()) { - AMBTCStatusQueryResponse sqr = (AMBTCStatusQueryResponse)ambtc; - processBroadcast(ambtc, DecodeEventType.STATUS, - "UNIT:" + sqr.getUnitStatus() + " USER:" + sqr.getUserStatus()); + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + if(ambtc instanceof AMBTCUnitToUnitVoiceServiceChannelGrantUpdate upd) + { + processChannelUpdate(upd.getChannel(), upd.getServiceOptions(), upd.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + } + break; + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + if(ambtc instanceof AMBTCTelephoneInterconnectChannelGrantUpdate upd) + { + processChannelUpdate(upd.getChannel(), upd.getServiceOptions(), upd.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + } + break; } } - private void processBroadcast(AMBTCMessage ambtcMessage, DecodeEventType request, String details) { - processBroadcast(ambtcMessage.getIdentifiers(), ambtcMessage.getTimestamp(), request, details); - } - - private void processBroadcast(TSBKMessage tsbkMessage, DecodeEventType request, String details) { - processBroadcast(tsbkMessage.getIdentifiers(), tsbkMessage.getTimestamp(), request, details); - } - - private void processBroadcast(List identifiers, long timestamp, DecodeEventType request, String s) { - MutableIdentifierCollection requestCollection = getMutableIdentifierCollection(identifiers); - - broadcast(P25DecodeEvent.builder(request, timestamp) - .channel(getCurrentChannel()) - .details(s) - .identifiers(requestCollection) - .build()); - } - - private MutableIdentifierCollection getMutableIdentifierCollection(List identifiers) { - MutableIdentifierCollection requestCollection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - requestCollection.remove(IdentifierClass.USER); - requestCollection.update(identifiers); - return requestCollection; - } - - private void processAMBTCIspAuthenticationResponse(P25Message message, AMBTCMessage ambtc) { - if(message instanceof AMBTCAuthenticationResponse) + /** + * AMBTC Channel Grants + */ + private void processAMBTCChannelGrant(AMBTCMessage ambtc) + { + switch(ambtc.getHeader().getOpcode()) { - AMBTCAuthenticationResponse ar = (AMBTCAuthenticationResponse)ambtc; - processBroadcast(ambtc, DecodeEventType.RESPONSE, "AUTHENTICATION:" + ar.getAuthenticationValue()); + case OSP_GROUP_DATA_CHANNEL_GRANT: + if(ambtc instanceof AMBTCGroupDataChannelGrant gdcg) + { + processChannelGrant(gdcg.getChannel(), gdcg.getServiceOptions(), gdcg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + } + break; + case OSP_GROUP_VOICE_CHANNEL_GRANT: + if(ambtc instanceof AMBTCGroupVoiceChannelGrant gvcg) + { + processChannelGrant(gvcg.getChannel(), gvcg.getServiceOptions(), gvcg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + } + break; + case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: + if(ambtc instanceof AMBTCIndividualDataChannelGrant idcg) + { + processChannelGrant(idcg.getChannel(), idcg.getServiceOptions(), idcg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + } + break; + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + if(ambtc instanceof AMBTCTelephoneInterconnectChannelGrant ticg) + { + processChannelGrant(ticg.getChannel(), ticg.getServiceOptions(), ticg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + } + break; + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + if(ambtc instanceof AMBTCUnitToUnitVoiceServiceChannelGrant uuvscg) + { + processChannelGrant(uuvscg.getChannel(), uuvscg.getServiceOptions(), uuvscg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + } + break; + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT: + if(ambtc instanceof AMBTCMotorolaGroupRegroupChannelGrant mgrcg) + { + processChannelGrant(mgrcg.getChannel(), mgrcg.getServiceOptions(), mgrcg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + } + break; } } /** * Processes a Header Data Unit message and starts a new call event. */ - private void processHDU(HDUMessage message) + private void processHDU(IMessage message) { - if(message.isValid()) + if(message.isValid() && message instanceof HDUMessage hdu) { - HeaderData headerData = message.getHeaderData(); - - if(headerData.isValid()) - { - closeCurrentCallEvent(message.getTimestamp()); - - for(Identifier identifier : headerData.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } - - updateCurrentCall(headerData.isEncryptedAudio() ? DecodeEventType.CALL_ENCRYPTED : - DecodeEventType.CALL, null, message.getTimestamp()); - - return; - } + HeaderData headerData = hdu.getHeaderData(); + ServiceOptions serviceOptions = headerData.isEncryptedAudio() ? + VoiceServiceOptions.createEncrypted() : VoiceServiceOptions.createUnencrypted(); + MutableIdentifierCollection mic = getMutableIdentifierCollection(hdu.getIdentifiers(), message.getTimestamp()); + String details = headerData.isEncryptedAudio() ? headerData.getEncryptionKey().toString() : null; + DecodeEventType type = headerData.isEncryptedAudio() ? DecodeEventType.CALL_ENCRYPTED : DecodeEventType.CALL; + mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), getCurrentChannel(), type, + serviceOptions, mic, message.getTimestamp(), details); } - broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); + broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); } @@ -714,131 +788,104 @@ private void processHDU(HDUMessage message) * * @param message that is an instance of an LDU1 or LDU2 message */ - private void processLDU(P25Message message) + private void processLDU(P25P1Message message) { - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); - - if(message instanceof LDU1Message) + if(message instanceof LDU1Message ldu1) { - LinkControlWord lcw = ((LDU1Message)message).getLinkControlWord(); + LinkControlWord lcw = ldu1.getLinkControlWord(); if(lcw != null && lcw.isValid()) { - processLinkControl(lcw, message.getTimestamp()); + processLC(lcw, message.getTimestamp(), false); } - - updateCurrentCall(DecodeEventType.CALL, null, message.getTimestamp()); } - else if(message instanceof LDU2Message) + else if(message instanceof LDU2Message ldu2) { - EncryptionSyncParameters esp = ((LDU2Message)message).getEncryptionSyncParameters(); + EncryptionSyncParameters esp = ldu2.getEncryptionSyncParameters(); if(esp != null && esp.isValid()) { - processEncryptionSyncParameters(esp, message.getTimestamp()); + if(esp.isEncryptedAudio()) + { + getIdentifierCollection().update(esp.getIdentifiers()); + mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), esp.getEncryptionKey(), + message.getTimestamp()); + } + else + { + getIdentifierCollection().remove(Form.ENCRYPTION_KEY); + mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), null, + message.getTimestamp()); + } + } + else + { + mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), null, message.getTimestamp()); } - - updateCurrentCall(DecodeEventType.CALL, null, message.getTimestamp()); } + + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); + } + + /** + * Process Terminator Data Unit (TDU). + */ + private void processTDU(P25P1Message message) + { + mTrafficChannelManager.closeP1CallEvent(getCurrentFrequency(), message.getTimestamp()); + getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); + broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); } /** - * Processes a Terminator Data Unit with Link Control (TDULC) message and forwards valid - * Link Control Word messages for additional processing. + * Process Terminator Data Unit with Link Control (TDULC) message and forwards valid Link Control Word message for + * additional processing. * * @param message that is an instance of a TDULC */ - private void processTDULC(P25Message message) + private void processTDULC(P25P1Message message) { - closeCurrentCallEvent(message.getTimestamp()); - broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); + mTrafficChannelManager.closeP1CallEvent(getCurrentFrequency(), message.getTimestamp()); + getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); - if(message instanceof TDULinkControlMessage) + if(message instanceof TDULinkControlMessage tdulc) { - LinkControlWord lcw = ((TDULinkControlMessage)message).getLinkControlWord(); + LinkControlWord lcw = tdulc.getLinkControlWord(); if(lcw != null && lcw.isValid()) { - processLinkControl(lcw, message.getTimestamp()); + //Send an ACTIVE decoder state event for everything except the CALL TERMINATION opcode which is + //handled by the processLC() method. + if(lcw.getOpcode() != LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION) + { + //Set the state to ACTIVE while the call continues in hangtime. The processLC() method will signal + // the channel teardown. + broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); + } + + processLC(lcw, message.getTimestamp(), true); } } } /** - * Updates or creates a current call event. + * Packet Data Unit * - * @param decodeEventType of call that will be used as an event description - * @param details of the call (optional) - * @param timestamp of the message indicating a call or continuation + * @param message */ - private void updateCurrentCall(DecodeEventType decodeEventType, String details, long timestamp) + private void processPDU(P25P1Message message) { - if(mCurrentCallEvent == null) + if(message.isValid() && message instanceof PDUMessage pdu) { - mCurrentCallEvent = P25DecodeEvent.builder(DecodeEventType.CALL, timestamp) - .channel(getCurrentChannel()) - .details(details) - .identifiers(getIdentifierCollection().copyOf()) - .build(); - - broadcast(mCurrentCallEvent); - broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); + broadcastEvent(pdu.getIdentifiers(), message.getTimestamp(), DecodeEventType.DATA_PACKET, pdu.toString()); } - else + else if(message.isValid() && message instanceof ResponseMessage response) { - mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf()); - mCurrentCallEvent.end(timestamp); - broadcast(mCurrentCallEvent); - - if(decodeEventType == DecodeEventType.CALL_ENCRYPTED) - { - mCurrentCallEvent.setDetails(details); - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED)); - } - else - { - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); - } + broadcastEvent(message.getIdentifiers(), message.getTimestamp(), DecodeEventType.RESPONSE, response.getResponseText()); } - } - - /** - * Ends/closes the current call event. - * - * @param timestamp of the message that indicates the event has ended. - */ - private void closeCurrentCallEvent(long timestamp) - { - if(mCurrentCallEvent != null) + else if(message.isValid() && message instanceof PDUSequenceMessage pdu && pdu.getPDUSequence().isComplete()) { - mCurrentCallEvent.end(timestamp); - broadcast(mCurrentCallEvent); - mCurrentCallEvent = null; - - //Only clear the from identifier at this point ... the channel may still be allocated to the TO talkgroup - getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); - } - } - - /** - * Terminator Data Unit (TDU). - */ - private void processTDU(P25Message message) - { - closeCurrentCallEvent(message.getTimestamp()); - broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); - } - - /** - * Packet Data Unit - * - * @param message - */ - private void processPDU(P25Message message) - { - if(message.isValid() && message instanceof PDUMessage pdu) - { - processBroadcast(pdu.getIdentifiers(), message.getTimestamp(), DecodeEventType.DATA_PACKET, pdu.toString()); + broadcastEvent(pdu.getIdentifiers(), message.getTimestamp(), DecodeEventType.DATA_PACKET, pdu.toString()); } broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); @@ -849,11 +896,12 @@ private void processPDU(P25Message message) * * @param message */ - private void processUMBTC(P25Message message) + private void processUMBTC(P25P1Message message) { if(message.isValid() && message instanceof UMBTCTelephoneInterconnectRequestExplicitDialing tired) { - processBroadcast(tired.getIdentifiers(), tired.getTimestamp(), DecodeEventType.REQUEST, "TELEPHONE INTERCONNECT:" + tired.getTelephoneNumber()); + broadcastEvent(message.getIdentifiers(), message.getTimestamp(), DecodeEventType.REQUEST, + "TELEPHONE INTERCONNECT:" + tired.getTelephoneNumber()); } broadcast(new DecoderStateEvent(this, Event.DECODE, State.CONTROL)); @@ -864,79 +912,60 @@ private void processUMBTC(P25Message message) * * @param message */ - private void processPacketData(P25Message message) + private void processPacketData(P25P1Message message) { broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); - if(message instanceof SNDCPPacketMessage) + if(message instanceof SNDCPPacketMessage sndcp) { - SNDCPPacketMessage sndcp = (SNDCPPacketMessage) message; - getIdentifierCollection().update(sndcp.getIdentifiers()); + processSNDCP(sndcp); } - else if(message instanceof PacketMessage) + else if(message instanceof PacketMessage packetMessage) { - PacketMessage packetMessage = (PacketMessage) message; - getIdentifierCollection().remove(IdentifierClass.USER); - getIdentifierCollection().update(packetMessage.getIdentifiers()); - IPacket packet = packetMessage.getPacket(); - if(packet instanceof IPV4Packet) + if(packet instanceof IPV4Packet ipv4) { - IPV4Packet ipv4 = (IPV4Packet) packet; - IPacket ipPayload = ipv4.getPayload(); - if(ipPayload instanceof UDPPacket) + if(ipPayload instanceof UDPPacket udpPacket) { - UDPPacket udpPacket = (UDPPacket) ipPayload; - IPacket udpPayload = udpPacket.getPayload(); - if(udpPayload instanceof ARSPacket) + if(udpPayload instanceof ARSPacket arsPacket) { - ARSPacket arsPacket = (ARSPacket) udpPayload; - - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - for(Identifier identifier : packet.getIdentifiers()) - { - ic.update(identifier); - } + MutableIdentifierCollection mic = getMutableIdentifierCollection(message.getIdentifiers(), message.getTimestamp()); - DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE, message.getTimestamp()) + DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE, + message.getTimestamp()) .channel(getCurrentChannel()) - .identifiers(ic) + .identifiers(mic) .details(arsPacket + " " + ipv4) .build(); broadcast(packetEvent); } - else if(udpPayload instanceof MCGPPacket) + else if(udpPayload instanceof MCGPPacket mcgp) { - MCGPPacket mcgpPacket = (MCGPPacket) udpPayload; + MutableIdentifierCollection mic = getMutableIdentifierCollection(message.getIdentifiers(), message.getTimestamp()); - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - for(Identifier identifier : packet.getIdentifiers()) - { - ic.update(identifier); - } - - DecodeEvent cellocatorEvent = P25DecodeEvent.builder(DecodeEventType.CELLOCATOR, message.getTimestamp()) + DecodeEvent cellocatorEvent = P25DecodeEvent.builder(DecodeEventType.CELLOCATOR, + message.getTimestamp()) .channel(getCurrentChannel()) - .identifiers(ic) - .details(mcgpPacket + " " + ipv4) + .identifiers(mic) + .details(mcgp + " " + ipv4) .build(); broadcast(cellocatorEvent); } else if(udpPayload instanceof LRRPPacket lrrpPacket) { - MutableIdentifierCollection ic = new MutableIdentifierCollection(packet.getIdentifiers()); + MutableIdentifierCollection mic = getMutableIdentifierCollection(message.getIdentifiers(), message.getTimestamp()); DecodeEvent lrrpEvent = P25DecodeEvent.builder(DecodeEventType.LRRP, message.getTimestamp()) .channel(getCurrentChannel()) .details(lrrpPacket + " " + ipv4) - .identifiers(ic) + .identifiers(mic) .protocol(Protocol.LRRP) .build(); @@ -949,7 +978,7 @@ else if(udpPayload instanceof LRRPPacket lrrpPacket) PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent .plottableBuilder(DecodeEventType.GPS, message.getTimestamp()) .channel(getCurrentChannel()) - .identifiers(ic) + .identifiers(mic) .protocol(Protocol.LRRP) .location(geoPosition) .build(); @@ -959,15 +988,11 @@ else if(udpPayload instanceof LRRPPacket lrrpPacket) } else { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - for(Identifier identifier : packet.getIdentifiers()) - { - ic.update(identifier); - } + MutableIdentifierCollection mic = getMutableIdentifierCollection(message.getIdentifiers(), message.getTimestamp()); DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.UDP_PACKET, message.getTimestamp()) .channel(getCurrentChannel()) - .identifiers(ic) + .identifiers(mic) .details(ipv4.toString()) .build(); @@ -976,15 +1001,11 @@ else if(udpPayload instanceof LRRPPacket lrrpPacket) } else if(ipPayload instanceof ICMPPacket) { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - for(Identifier identifier : packet.getIdentifiers()) - { - ic.update(identifier); - } + MutableIdentifierCollection mic = getMutableIdentifierCollection(message.getIdentifiers(), message.getTimestamp()); DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.ICMP_PACKET, message.getTimestamp()) .channel(getCurrentChannel()) - .identifiers(ic) + .identifiers(mic) .details(ipv4.toString()) .build(); @@ -992,15 +1013,11 @@ else if(ipPayload instanceof ICMPPacket) } else { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - for(Identifier identifier : packet.getIdentifiers()) - { - ic.update(identifier); - } + MutableIdentifierCollection mic = getMutableIdentifierCollection(message.getIdentifiers(), message.getTimestamp()); DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.IP_PACKET, message.getTimestamp()) .channel(getCurrentChannel()) - .identifiers(ic) + .identifiers(mic) .details(ipv4.toString()) .build(); @@ -1009,15 +1026,11 @@ else if(ipPayload instanceof ICMPPacket) } else if(packet instanceof UnknownPacket) { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - for(Identifier identifier : packet.getIdentifiers()) - { - ic.update(identifier); - } + MutableIdentifierCollection mic = getMutableIdentifierCollection(message.getIdentifiers(), message.getTimestamp()); DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, message.getTimestamp()) .channel(getCurrentChannel()) - .identifiers(ic) + .identifiers(mic) .details(packet.toString()) .build(); @@ -1031,99 +1044,97 @@ else if(packet instanceof UnknownPacket) * * @param message to process */ - private void processSNDCP(P25Message message) + private void processSNDCP(P25P1Message message) { broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA)); if(message.isValid() && message instanceof SNDCPPacketMessage sndcpPacket) { - MutableIdentifierCollection ic = getMutableIdentifierCollection(sndcpPacket.getIdentifiers()); - - switch(sndcpPacket.getSNDCPPacketHeader().getPDUType()) + switch(sndcpPacket.getSNDCPMessage().getPDUType()) { case OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT: - processBroadcast(sndcpPacket.getIdentifiers(), message.getTimestamp(), + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), DecodeEventType.RESPONSE, "SNDCP ACTIVATE TDS CONTEXT ACCEPT"); break; case OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: - processBroadcast(sndcpPacket.getIdentifiers(), message.getTimestamp(), + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), DecodeEventType.RESPONSE, "SNDCP DEACTIVATE TDS CONTEXT ACCEPT"); break; case OUTBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: - processBroadcast(sndcpPacket.getIdentifiers(), message.getTimestamp(), + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), DecodeEventType.REQUEST, "SNDCP DEACTIVATE TDS CONTEXT"); break; case OUTBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REJECT: - processBroadcast(sndcpPacket.getIdentifiers(), message.getTimestamp(), + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), DecodeEventType.RESPONSE, "SNDCP ACTIVATE TDS CONTEXT REJECT"); break; + case OUTBOUND_SNDCP_RF_UNCONFIRMED_DATA: + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), + DecodeEventType.REQUEST, sndcpPacket.toString()); + break; + case OUTBOUND_SNDCP_RF_CONFIRMED_DATA: + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), + DecodeEventType.REQUEST, sndcpPacket.toString()); + break; + case OUTBOUND_UNKNOWN: + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), + DecodeEventType.REQUEST, sndcpPacket.toString()); + break; case INBOUND_SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST: - processBroadcast(sndcpPacket.getIdentifiers(), message.getTimestamp(), + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), DecodeEventType.REQUEST, "SNDCP ACTIVATE TDS CONTEXT"); break; case INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: - processBroadcast(sndcpPacket.getIdentifiers(), message.getTimestamp(), + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), DecodeEventType.RESPONSE, "SNDCP DEACTIVATE TDS CONTEXT ACCEPT"); break; case INBOUND_SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: - processBroadcast(sndcpPacket.getIdentifiers(), message.getTimestamp(), + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), DecodeEventType.REQUEST, "SNDCP DEACTIVATE TDS CONTEXT"); break; + case INBOUND_SNDCP_RF_CONFIRMED_DATA: + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), + DecodeEventType.REQUEST, sndcpPacket.toString()); + break; + case INBOUND_UNKNOWN: + broadcastEvent(sndcpPacket.getIdentifiers(), message.getTimestamp(), + DecodeEventType.REQUEST, sndcpPacket.toString()); + break; } } } /** - * Trunking Signalling Block (TSBK) - * - * @param message + * Trunking Signalling Block (TSBK) messages */ - private void processTSBK(P25Message message) + private void processTSBK(P25P1Message message) { broadcast(new DecoderStateEvent(this, Event.DECODE, State.CONTROL)); - if(message.isValid() && message instanceof TSBKMessage) + if(message.isValid() && message instanceof TSBKMessage tsbk) { - TSBKMessage tsbk = (TSBKMessage)message; - switch(tsbk.getOpcode()) { //Channel Grant messages case OSP_GROUP_DATA_CHANNEL_GRANT: - processTSBKDataChannelGrant(tsbk); - break; case OSP_GROUP_VOICE_CHANNEL_GRANT: - processTSBKGroupVoiceChannelGrant(tsbk); - break; - case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE: - processTSBKGroupVoiceChannelGrantUpdate(tsbk); - break; - case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: - processTSBKGroupVoiceChannelGrantUpdateExplicit(tsbk); - break; case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - processTSBKUnitToUnitVoiceChannelGrant(tsbk); - break; - case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: - processTSBKUnitToUnitVoiceChannelGrantUpdate(tsbk); - break; case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - processTSBKTelephoneInterconnectVoiceChannelGrant(tsbk); - break; - case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: - processTSBKTelephoneInterconnectVoiceChannelGrantUpdate(tsbk); - break; case OSP_SNDCP_DATA_CHANNEL_GRANT: - processTSBKSndcpDataChannelGrant(tsbk); - break; - case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT: - processTSBKMotorolaOspPatchGroupChannelGrant(tsbk); + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT: + processTSBKChannelGrant(tsbk); break; - case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE: - processTSBKMotorolaOspPatchGroupChannelGrantUpdate(tsbk); + + //Channel Grant Update messages + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE: + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE: + processTSBKChannelGrantUpdate(tsbk); break; - //Network Configuration Messages + //Network Configuration Messages case MOTOROLA_OSP_TRAFFIC_CHANNEL_ID: case MOTOROLA_OSP_SYSTEM_LOADING: case MOTOROLA_OSP_BASE_STATION_ID: @@ -1136,42 +1147,84 @@ private void processTSBK(P25Message message) case OSP_TDMA_SYNC_BROADCAST: case OSP_SYSTEM_SERVICE_BROADCAST: case OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST: - case OSP_RFSS_STATUS_BROADCAST: - case OSP_NETWORK_STATUS_BROADCAST: case OSP_ADJACENT_STATUS_BROADCAST: case OSP_IDENTIFIER_UPDATE: - case OSP_PROTECTION_PARAMETER_BROADCAST: - case OSP_PROTECTION_PARAMETER_UPDATE: + case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN: + case OSP_RESERVED_3F: + mNetworkConfigurationMonitor.process(tsbk); + + //Send the frequency bands to the traffic channel manager to use for traffic channel preload data + if(tsbk instanceof IFrequencyBand frequencyBand) + { + mTrafficChannelManager.processFrequencyBand(frequencyBand); + } + break; + case OSP_NETWORK_STATUS_BROADCAST: + if((getCurrentChannel() == null || getCurrentChannel().getDownlinkFrequency() > 0) && + mChannel.isStandardChannel() && tsbk instanceof NetworkStatusBroadcast nsb && + nsb.getChannel().getDownlinkFrequency() > 0) + { + setCurrentChannel(nsb.getChannel()); + DecoderLogicalChannelNameIdentifier channelID = + DecoderLogicalChannelNameIdentifier.create(nsb.getChannel().toString(), Protocol.APCO25); + getIdentifierCollection().update(channelID); + setCurrentFrequency(nsb.getChannel().getDownlinkFrequency()); + FrequencyConfigurationIdentifier frequencyID = FrequencyConfigurationIdentifier + .create(nsb.getChannel().getDownlinkFrequency()); + getIdentifierCollection().update(frequencyID); + + } + mNetworkConfigurationMonitor.process(tsbk); + break; + case OSP_RFSS_STATUS_BROADCAST: + if((getCurrentChannel() == null || getCurrentChannel().getDownlinkFrequency() > 0) && + mChannel.isStandardChannel() && tsbk instanceof RFSSStatusBroadcast rfss && + rfss.getChannel().getDownlinkFrequency() > 0) + { + setCurrentChannel(rfss.getChannel()); + DecoderLogicalChannelNameIdentifier channelID = + DecoderLogicalChannelNameIdentifier.create(rfss.getChannel().toString(), Protocol.APCO25); + getIdentifierCollection().update(channelID); + setCurrentFrequency(rfss.getChannel().getDownlinkFrequency()); + FrequencyConfigurationIdentifier frequencyID = FrequencyConfigurationIdentifier + .create(rfss.getChannel().getDownlinkFrequency()); + getIdentifierCollection().update(frequencyID); + } mNetworkConfigurationMonitor.process(tsbk); break; case OSP_UNIT_TO_UNIT_ANSWER_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.PAGE, "UNIT-TO-UNIT ANSWER REQUEST"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.PAGE, + "UNIT-TO-UNIT ANSWER REQUEST"); break; case OSP_TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - processTSBKTelephoneInterconnectAnswerRequest(tsbk); + if(tsbk instanceof TelephoneInterconnectAnswerRequest tiar) + { + broadcastEvent(tsbk, DecodeEventType.PAGE, "TELEPHONE ANSWER REQUEST: " + + tiar.getTelephoneNumber()); + } break; case OSP_SNDCP_DATA_PAGE_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.PAGE, "SNDCP DATA PAGE REQUEST"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.PAGE, + "SNDCP DATA PAGE REQUEST"); break; case OSP_STATUS_UPDATE: - processTSBKStatusUpdate(tsbk); - break; case OSP_STATUS_QUERY: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.QUERY, "STATUS"); + processTSBKStatus(tsbk); break; case OSP_MESSAGE_UPDATE: - if(tsbk instanceof MessageUpdate) + if(tsbk instanceof MessageUpdate mu) { - MessageUpdate mu = (MessageUpdate)tsbk; - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.SDM, "MSG:" + mu.getShortDataMessage()); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.SDM, "MSG:" + + mu.getShortDataMessage()); } break; case OSP_RADIO_UNIT_MONITOR_COMMAND: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.COMMAND, "RADIO UNIT MONITOR"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.COMMAND, + "RADIO UNIT MONITOR"); break; case OSP_CALL_ALERT: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.PAGE, "CALL ALERT"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.PAGE, "CALL ALERT"); break; case OSP_ACKNOWLEDGE_RESPONSE: processTSBKAcknowledgeResponse(tsbk); @@ -1189,7 +1242,8 @@ private void processTSBK(P25Message message) processTSBKGroupAffiliationResponse(tsbk); break; case OSP_GROUP_AFFILIATION_QUERY: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.QUERY, "GROUP AFFILIATION"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.QUERY, + "GROUP AFFILIATION"); break; case OSP_LOCATION_REGISTRATION_RESPONSE: processTSBKLocationRegistrationResponse(tsbk); @@ -1198,38 +1252,40 @@ private void processTSBK(P25Message message) processTSBKUnitRegistrationResponse(tsbk); break; case OSP_UNIT_REGISTRATION_COMMAND: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.COMMAND, "UNIT REGISTRATION"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.COMMAND, + "UNIT REGISTRATION"); break; case OSP_AUTHENTICATION_COMMAND: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.COMMAND, "AUTHENTICATE"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.COMMAND, + "AUTHENTICATE"); break; case OSP_UNIT_DEREGISTRATION_ACKNOWLEDGE: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.DEREGISTER, "ACKNOWLEDGE UNIT DE-REGISTRATION"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.DEREGISTER, + "ACKNOWLEDGE UNIT DE-REGISTRATION"); break; case OSP_ROAMING_ADDRESS_COMMAND: - if(tsbk instanceof RoamingAddressCommand) + if(tsbk instanceof RoamingAddressCommand rac) { - RoamingAddressCommand rac = (RoamingAddressCommand)tsbk; - - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.COMMAND, rac.getStackOperation() + " ROAMING ADDRESS"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.COMMAND, + rac.getStackOperation() + " ROAMING ADDRESS"); } break; //MOTOROLA PATCH GROUP OPCODES - case MOTOROLA_OSP_PATCH_GROUP_ADD: - mPatchGroupManager.addPatchGroups(tsbk.getIdentifiers()); + case MOTOROLA_OSP_GROUP_REGROUP_ADD: + mPatchGroupManager.addPatchGroups(tsbk.getIdentifiers(), message.getTimestamp()); break; - case MOTOROLA_OSP_PATCH_GROUP_DELETE: + case MOTOROLA_OSP_GROUP_REGROUP_DELETE: mPatchGroupManager.removePatchGroups(tsbk.getIdentifiers()); break; //L3HARRIS PATCH GROUP OPCODES case HARRIS_OSP_GRG_EXENC_CMD: - if(tsbk instanceof HarrisGroupRegroupExplicitEncryptionCommand regroup) + if(tsbk instanceof L3HarrisGroupRegroupExplicitEncryptionCommand regroup) { if(regroup.getRegroupOptions().isActivate()) { - mPatchGroupManager.addPatchGroup(regroup.getPatchGroup()); + mPatchGroupManager.addPatchGroup(regroup.getPatchGroup(), tsbk.getTimestamp()); } else { @@ -1240,47 +1296,48 @@ private void processTSBK(P25Message message) //STANDARD - INBOUND OPCODES case ISP_GROUP_VOICE_SERVICE_REQUEST: - if(tsbk instanceof GroupVoiceServiceRequest) + if(tsbk instanceof GroupVoiceServiceRequest gvsr) { - GroupVoiceServiceRequest gvsr = (GroupVoiceServiceRequest)tsbk; - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "GROUP VOICE SERVICE " + gvsr.getVoiceServiceOptions()); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "GROUP VOICE SERVICE " + gvsr.getServiceOptions()); } break; case ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST: - if(tsbk instanceof UnitToUnitVoiceServiceRequest) + if(tsbk instanceof UnitToUnitVoiceServiceRequest uuvsr) { - UnitToUnitVoiceServiceRequest uuvsr = (UnitToUnitVoiceServiceRequest)tsbk; - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "UNIT-2-UNIT VOICE SERVICE " + uuvsr.getVoiceServiceOptions()); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "UNIT-2-UNIT VOICE SERVICE " + uuvsr.getServiceOptions()); } break; case ISP_UNIT_TO_UNIT_ANSWER_RESPONSE: processTSBKUnitToUnitAnswerResponse(tsbk); break; case ISP_TELEPHONE_INTERCONNECT_PSTN_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "TELEPHONE INTERCONNECT"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "TELEPHONE INTERCONNECT"); break; case ISP_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE: processTSBKTelephoneInterconnectAnswerResponse(tsbk); break; case ISP_INDIVIDUAL_DATA_SERVICE_REQUEST: - if(tsbk instanceof IndividualDataServiceRequest) + if(tsbk instanceof IndividualDataServiceRequest idsr) { - IndividualDataServiceRequest idsr = (IndividualDataServiceRequest)tsbk; - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "INDIVIDUAL DATA SERVICE " + idsr.getVoiceServiceOptions()); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "INDIVIDUAL DATA SERVICE " + idsr.getServiceOptions()); } break; case ISP_GROUP_DATA_SERVICE_REQUEST: - if(tsbk instanceof GroupDataServiceRequest) + if(tsbk instanceof GroupDataServiceRequest gdsr) { - GroupDataServiceRequest gdsr = (GroupDataServiceRequest)tsbk; - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "GROUP DATA SERVICE " + gdsr.getVoiceServiceOptions()); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "GROUP DATA SERVICE " + gdsr.getServiceOptions()); } break; case ISP_SNDCP_DATA_CHANNEL_REQUEST: - if(tsbk instanceof SNDCPDataChannelRequest) + if(tsbk instanceof SNDCPDataChannelRequest sdcr) { - SNDCPDataChannelRequest sdcr = (SNDCPDataChannelRequest)tsbk; - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "SNDCP DATA CHANNEL " + sdcr.getDataServiceOptions()); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "SNDCP DATA CHANNEL " + sdcr.getDataServiceOptions()); } break; case ISP_SNDCP_DATA_PAGE_RESPONSE: @@ -1290,531 +1347,456 @@ private void processTSBK(P25Message message) processTSBKSndcpReconnectRequest(tsbk); break; case ISP_STATUS_UPDATE_REQUEST: - processTSBKStatusUpdateRequest(tsbk); - break; case ISP_STATUS_QUERY_RESPONSE: - processTSBKStatusQueryResponse(tsbk); - break; case ISP_STATUS_QUERY_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.QUERY, "UNIT AND USER STATUS"); + processTSBKStatus(tsbk); break; case ISP_MESSAGE_UPDATE_REQUEST: - if(tsbk instanceof MessageUpdateRequest) + if(tsbk instanceof MessageUpdateRequest mur) { - MessageUpdateRequest mur = (MessageUpdateRequest)tsbk; - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.SDM, "MESSAGE:" + mur.getShortDataMessage()); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.SDM, + "MESSAGE:" + mur.getShortDataMessage()); } break; case ISP_RADIO_UNIT_MONITOR_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "RADIO UNIT MONITOR"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "RADIO UNIT MONITOR"); break; case ISP_CALL_ALERT_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "CALL ALERT"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "CALL ALERT"); break; case ISP_UNIT_ACKNOWLEDGE_RESPONSE: - if(tsbk instanceof UnitAcknowledgeResponse) + if(tsbk instanceof UnitAcknowledgeResponse uar) { - UnitAcknowledgeResponse uar = (UnitAcknowledgeResponse)tsbk; - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.RESPONSE, "UNIT ACKNOWLEDGE:" + uar.getAcknowledgedServiceType().getDescription()); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.RESPONSE, + "UNIT ACKNOWLEDGE:" + uar.getAcknowledgedServiceType().getDescription()); } break; case ISP_CANCEL_SERVICE_REQUEST: - processTSBKCancelServiceRequest(tsbk); + if(tsbk instanceof CancelServiceRequest csr) + { + broadcastEvent(tsbk, DecodeEventType.REQUEST, "CANCEL SERVICE:" + csr.getServiceType() + + " REASON:" + csr.getCancelReason() + (csr.hasAdditionalInformation() ? " INFO:" + + csr.getAdditionalInformation() : "")); + } break; case ISP_EXTENDED_FUNCTION_RESPONSE: - processTSBKExtendedFunctionResponse(tsbk); + if(tsbk instanceof ExtendedFunctionResponse efr) + { + broadcastEvent(tsbk, DecodeEventType.RESPONSE, "EXTENDED FUNCTION:" + + efr.getExtendedFunction() + " ARGUMENTS:" + efr.getArguments()); + } break; case ISP_EMERGENCY_ALARM_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "EMERGENCY ALARM"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "EMERGENCY ALARM"); break; case ISP_GROUP_AFFILIATION_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "GROUP AFFILIATION"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "GROUP AFFILIATION"); break; case ISP_GROUP_AFFILIATION_QUERY_RESPONSE: - processTSBKGroupAffiliationQueryResponse(tsbk); + if(tsbk instanceof GroupAffiliationQueryResponse gaqr) + { + broadcastEvent(tsbk, DecodeEventType.RESPONSE, "AFFILIATION - GROUP:" + + gaqr.getGroupAddress() + " ANNOUNCEMENT GROUP:" + gaqr.getAnnouncementGroupAddress()); + } break; case ISP_UNIT_DE_REGISTRATION_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.DEREGISTER, "UNIT DE-REGISTRATION REQUEST"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.DEREGISTER, + "UNIT DE-REGISTRATION REQUEST"); break; case ISP_UNIT_REGISTRATION_REQUEST: - processTSBKUnitRegistrationRequest(tsbk); + if(tsbk instanceof UnitRegistrationRequest urr) + { + broadcastEvent(tsbk, DecodeEventType.REGISTER, (urr.isEmergency() ? "EMERGENCY " : "") + + "UNIT REGISTRATION REQUEST - CAPABILITY:" + urr.getCapability()); + } break; case ISP_LOCATION_REGISTRATION_REQUEST: - processTSBKLocationRegistrationRequest(tsbk); + if(tsbk instanceof LocationRegistrationRequest lrr) + { + broadcastEvent(tsbk, DecodeEventType.REGISTER, (lrr.isEmergency() ? "EMERGENCY " : "") + + "LOCATION REGISTRATION REQUEST - CAPABILITY:" + lrr.getCapability()); + } break; case ISP_PROTECTION_PARAMETER_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "ENCRYPTION PARAMETERS"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "ENCRYPTION PARAMETERS"); break; case ISP_IDENTIFIER_UPDATE_REQUEST: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, "FREQUENCY BAND DETAILS"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "FREQUENCY BAND DETAILS"); break; case ISP_ROAMING_ADDRESS_REQUEST: - processTSBKRoamingAddressRequest(tsbk); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.REQUEST, + "ROAMING ADDRESS"); break; case ISP_ROAMING_ADDRESS_RESPONSE: - processBroadcast(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.RESPONSE, "ROAMING ADDRESS"); + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.RESPONSE, + "ROAMING ADDRESS"); + break; + case MOTOROLA_OSP_ACKNOWLEDGE_RESPONSE: + processTSBKAcknowledgeResponse(tsbk); break; case MOTOROLA_OSP_DENY_RESPONSE: - processTSBKMotorolaOspDenyResponse(tsbk); + processTSBKDenyResponse(tsbk); + break; + case MOTOROLA_OSP_EMERGENCY_ALARM_ACTIVATION: + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.EMERGENCY, + "RADIO EMERGENCY ALARM ACTIVATION"); break; + case MOTOROLA_OSP_EXTENDED_FUNCTION_COMMAND: + processTSBKExtendedFunctionCommand(tsbk); + break; + case MOTOROLA_OSP_QUEUED_RESPONSE: + processTSBKQueuedResponse(tsbk); default: -// mLog.debug("Unrecognized TSBK Opcode: " + tsbk.getOpcode().name() + " VENDOR:" + tsbk.getVendor() + -// " OPCODE:" + tsbk.getOpcodeNumber()); + if(!tsbk.getOpcode().name().startsWith("ISP")) + { + LOGGING_SUPPRESSOR.info(tsbk.getOpcode().name() + tsbk.getMessage().toHexString(), + 1, "Unrecognized TSBK Opcode: " + tsbk.getOpcode().name() + + " VENDOR:" + tsbk.getVendor() + " OPCODE:" + tsbk.getOpcodeNumber() + + " MSG:" + tsbk.getMessage().toHexString()); + } break; } } } - private void processTSBKMotorolaOspDenyResponse(TSBKMessage tsbk) { - if(tsbk instanceof MotorolaDenyResponse) - { - MotorolaDenyResponse dr = (MotorolaDenyResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, - "DENY: " + dr.getDeniedServiceType().getDescription() + - " REASON: " + dr.getDenyReason() + " - INFO: " + dr.getAdditionalInfo()); - } - } - - private void processTSBKRoamingAddressRequest(TSBKMessage tsbk) { - //TODO: not sure if this should be used or not. - broadcast(P25DecodeEvent.builder(DecodeEventType.REQUEST, tsbk.getTimestamp()) - .channel(getCurrentChannel()) - .details("ROAMING ADDRESS") - // TODO: This identifierCollection is different from all the others. - .identifiers(new IdentifierCollection(tsbk.getIdentifiers())) - .build()); - } - - private void processTSBKLocationRegistrationRequest(TSBKMessage tsbk) { - if(tsbk instanceof LocationRegistrationRequest) - { - LocationRegistrationRequest lrr = (LocationRegistrationRequest)tsbk; - processBroadcast(tsbk, DecodeEventType.REGISTER, - (lrr.isEmergency() ? "EMERGENCY " : "") + - "LOCATION REGISTRATION REQUEST - CAPABILITY:" + lrr.getCapability()); - } - } - - private void processTSBKUnitRegistrationRequest(TSBKMessage tsbk) { - if(tsbk instanceof UnitRegistrationRequest) - { - UnitRegistrationRequest urr = (UnitRegistrationRequest)tsbk; - processBroadcast(tsbk, DecodeEventType.REGISTER, - (urr.isEmergency() ? "EMERGENCY " : "") + - "UNIT REGISTRATION REQUEST - CAPABILITY:" + urr.getCapability()); - } - } - - private void processTSBKGroupAffiliationQueryResponse(TSBKMessage tsbk) { - if(tsbk instanceof GroupAffiliationQueryResponse) - { - GroupAffiliationQueryResponse gaqr = (GroupAffiliationQueryResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, - "AFFILIATION - GROUP:" + gaqr.getGroupAddress() + - " ANNOUNCEMENT GROUP:" + gaqr.getAnnouncementGroupAddress()); - } - } - - private void processTSBKExtendedFunctionResponse(TSBKMessage tsbk) { - if(tsbk instanceof ExtendedFunctionResponse) - { - ExtendedFunctionResponse efr = (ExtendedFunctionResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, - "EXTENDED FUNCTION:" + efr.getExtendedFunction() + - " ARGUMENTS:" + efr.getArguments()); - } - } - - private void processTSBKCancelServiceRequest(TSBKMessage tsbk) { - if(tsbk instanceof CancelServiceRequest) - { - CancelServiceRequest csr = (CancelServiceRequest)tsbk; - processBroadcast(tsbk, DecodeEventType.REQUEST, - "CANCEL SERVICE:" + csr.getServiceType() + - " REASON:" + csr.getCancelReason() + (csr.hasAdditionalInformation() ? - " INFO:" + csr.getAdditionalInformation() : "")); - } - } - - private void processTSBKStatusQueryResponse(TSBKMessage tsbk) { - if(tsbk instanceof StatusQueryResponse) - { - StatusQueryResponse sqr = (StatusQueryResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.STATUS, - "UNIT:" + sqr.getUnitStatus() + " USER:" + sqr.getUserStatus()); - } - } - - private void processTSBKStatusUpdateRequest(TSBKMessage tsbk) { - if(tsbk instanceof StatusUpdateRequest) + /** + * TSBK Status messaging + */ + private void processTSBKStatus(TSBKMessage tsbk) + { + switch(tsbk.getOpcode()) { - StatusUpdateRequest sur = (StatusUpdateRequest)tsbk; - processBroadcast(tsbk, DecodeEventType.STATUS, - "UNIT:" + sur.getUnitStatus() + " USER:" + sur.getUserStatus()); + case ISP_STATUS_UPDATE_REQUEST: + if(tsbk instanceof StatusUpdateRequest sur) + { + broadcastEvent(tsbk, DecodeEventType.STATUS, "UNIT:" + sur.getUnitStatus() + " USER:" + + sur.getUserStatus()); + } + break; + case ISP_STATUS_QUERY_RESPONSE: + if(tsbk instanceof StatusQueryResponse sqr) + { + broadcastEvent(tsbk, DecodeEventType.STATUS, "UNIT:" + sqr.getUnitStatus() + " USER:" + + sqr.getUserStatus()); + } + break; + case ISP_STATUS_QUERY_REQUEST: + if(tsbk instanceof StatusQueryRequest sqr) + { + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.QUERY, + "UNIT AND USER STATUS"); + } + break; + case OSP_STATUS_QUERY: + if(tsbk instanceof StatusQuery sq) + { + broadcastEvent(tsbk.getIdentifiers(), tsbk.getTimestamp(), DecodeEventType.QUERY, + "UNIT AND USER STATUS"); + } + break; + case OSP_STATUS_UPDATE: + if(tsbk instanceof StatusUpdate su) + { + broadcastEvent(tsbk, DecodeEventType.STATUS, + "UNIT:" + su.getUnitStatus() + " USER:" + su.getUserStatus()); + } + break; } } - private void processTSBKSndcpReconnectRequest(TSBKMessage tsbk) { - if(tsbk instanceof SNDCPReconnectRequest) + private void processTSBKSndcpReconnectRequest(TSBKMessage tsbk) + { + if(tsbk instanceof SNDCPReconnectRequest srr) { - SNDCPReconnectRequest srr = (SNDCPReconnectRequest)tsbk; - processBroadcast(tsbk, DecodeEventType.REQUEST, + broadcastEvent(tsbk, DecodeEventType.REQUEST, "SNDCP RECONNECT " + (srr.hasDataToSend() ? "- DATA TO SEND " : "") + srr.getDataServiceOptions()) ; } } - private void processTSBKSndcpDataPageResponse(TSBKMessage tsbk) { - if(tsbk instanceof SNDCPDataPageResponse) + private void processTSBKSndcpDataPageResponse(TSBKMessage tsbk) + { + if(tsbk instanceof SNDCPDataPageResponse sdpr) { - SNDCPDataPageResponse sdpr = (SNDCPDataPageResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, + broadcastEvent(tsbk, DecodeEventType.RESPONSE, sdpr.getAnswerResponse() + " SNDCP DATA " + sdpr.getDataServiceOptions()); } } - private void processTSBKTelephoneInterconnectAnswerResponse(TSBKMessage tsbk) { - if(tsbk instanceof TelephoneInterconnectAnswerResponse) + private void processTSBKTelephoneInterconnectAnswerResponse(TSBKMessage tsbk) + { + if(tsbk instanceof TelephoneInterconnectAnswerResponse tiar) { - TelephoneInterconnectAnswerResponse tiar = (TelephoneInterconnectAnswerResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, - tiar.getAnswerResponse() + " TELEPHONE INTERCONNECT " + tiar.getVoiceServiceOptions()); + broadcastEvent(tsbk, DecodeEventType.RESPONSE, + tiar.getAnswerResponse() + " TELEPHONE INTERCONNECT " + tiar.getServiceOptions()); } } - private void processTSBKUnitToUnitAnswerResponse(TSBKMessage tsbk) { - if(tsbk instanceof UnitToUnitVoiceServiceAnswerResponse) + private void processTSBKUnitToUnitAnswerResponse(TSBKMessage tsbk) + { + if(tsbk instanceof UnitToUnitVoiceServiceAnswerResponse uuvsar) { - UnitToUnitVoiceServiceAnswerResponse uuvsar = (UnitToUnitVoiceServiceAnswerResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, - uuvsar.getAnswerResponse() + " UNIT-2-UNIT VOICE SERVICE " + uuvsar.getVoiceServiceOptions()); + broadcastEvent(tsbk, DecodeEventType.RESPONSE, + uuvsar.getAnswerResponse() + " UNIT-2-UNIT VOICE SERVICE " + uuvsar.getServiceOptions()); } } - private void processTSBKUnitRegistrationResponse(TSBKMessage tsbk) { - if(tsbk instanceof UnitRegistrationResponse) + private void processTSBKUnitRegistrationResponse(TSBKMessage tsbk) + { + if(tsbk instanceof UnitRegistrationResponse urr) { - UnitRegistrationResponse urr = (UnitRegistrationResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.REGISTER, - urr.getResponse() + " UNIT REGISTRATION - UNIT ID:" + urr.getTargetUniqueId()); + broadcastEvent(tsbk, DecodeEventType.REGISTER, urr.getResponse() + " UNIT REGISTRATION - UNIT ID:" + + urr.getRegisteredRadio()); } } - private void processTSBKLocationRegistrationResponse(TSBKMessage tsbk) { - if(tsbk instanceof LocationRegistrationResponse) + private void processTSBKLocationRegistrationResponse(TSBKMessage tsbk) + { + if(tsbk instanceof LocationRegistrationResponse lrr) { - LocationRegistrationResponse lrr = (LocationRegistrationResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.REGISTER, + broadcastEvent(tsbk, DecodeEventType.REGISTER, lrr.getResponse() + " LOCATION REGISTRATION - GROUP:" + lrr.getGroupAddress()); } } - private void processTSBKGroupAffiliationResponse(TSBKMessage tsbk) { - if(tsbk instanceof GroupAffiliationResponse) + private void processTSBKGroupAffiliationResponse(TSBKMessage tsbk) + { + if(tsbk instanceof GroupAffiliationResponse gar) { - GroupAffiliationResponse gar = (GroupAffiliationResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, gar.getAffiliationResponse() + + broadcastEvent(tsbk, DecodeEventType.RESPONSE, gar.getAffiliationResponse() + " AFFILIATION GROUP: " + gar.getGroupAddress() + (gar.isGlobalAffiliation() ? " (GLOBAL)" : " (LOCAL)") + " ANNOUNCEMENT GROUP:" + gar.getAnnouncementGroupAddress()); } } - private void processTSBKDenyResponse(TSBKMessage tsbk) { - if(tsbk instanceof DenyResponse) + private void processTSBKDenyResponse(TSBKMessage tsbk) + { + if(tsbk instanceof DenyResponse dr) { - DenyResponse dr = (DenyResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, + broadcastEvent(tsbk, DecodeEventType.RESPONSE, "DENY: " + dr.getDeniedServiceType().getDescription() + " REASON: " + dr.getDenyReason() + " - INFO: " + dr.getAdditionalInfo()); } - } - - private void processTSBKExtendedFunctionCommand(TSBKMessage tsbk) { - if(tsbk instanceof ExtendedFunctionCommand) - { - ExtendedFunctionCommand efc = (ExtendedFunctionCommand)tsbk; - processBroadcast(tsbk, DecodeEventType.COMMAND, - "EXTENDED FUNCTION: " + efc.getExtendedFunction() + - " ARGUMENTS:" + efc.getArguments()); - } - } - - private void processTSBKQueuedResponse(TSBKMessage tsbk) { - if(tsbk instanceof QueuedResponse) - { - QueuedResponse qr = (QueuedResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, - "QUEUED: " + qr.getQueuedResponseServiceType().getDescription() + - " REASON: " + qr.getQueuedResponseReason() + - " INFO: " + qr.getAdditionalInfo()); - } - } - - private void processTSBKAcknowledgeResponse(TSBKMessage tsbk) { - if(tsbk instanceof AcknowledgeResponse) - { - AcknowledgeResponse ar = (AcknowledgeResponse)tsbk; - processBroadcast(tsbk, DecodeEventType.RESPONSE, - "ACKNOWLEDGE " + ar.getAcknowledgedServiceType().getDescription()); - } - } - - private void processTSBKStatusUpdate(TSBKMessage tsbk) { - if(tsbk instanceof StatusUpdate) + else if(tsbk instanceof MotorolaDenyResponse mdr) { - StatusUpdate su = (StatusUpdate)tsbk; - processBroadcast(tsbk, DecodeEventType.STATUS, - "UNIT:" + su.getUnitStatus() + " USER:" + su.getUserStatus()); + broadcastEvent(tsbk, DecodeEventType.RESPONSE, "DENY: " + mdr.getDeniedServiceType().getDescription() + + " REASON: " + mdr.getDenyReason() + " - INFO: " + mdr.getAdditionalInfo()); } } - private void processTSBKTelephoneInterconnectAnswerRequest(TSBKMessage tsbk) { - if(tsbk instanceof TelephoneInterconnectAnswerRequest) + private void processTSBKExtendedFunctionCommand(TSBKMessage tsbk) + { + if(tsbk instanceof ExtendedFunctionCommand efc) { - TelephoneInterconnectAnswerRequest tiar = (TelephoneInterconnectAnswerRequest)tsbk; - processBroadcast(tsbk, DecodeEventType.PAGE, - "TELEPHONE ANSWER REQUEST: " + tiar.getTelephoneNumber()); + broadcastEvent(tsbk, DecodeEventType.COMMAND, "FUNCTION: " + efc.getExtendedFunction() + + " ARGUMENTS:" + efc.getArguments()); } - } - - private void processTSBKMotorolaOspPatchGroupChannelGrantUpdate(TSBKMessage tsbk) { - if(tsbk instanceof PatchGroupVoiceChannelGrantUpdate) + else if(tsbk instanceof MotorolaExtendedFunctionCommand mefc) { - PatchGroupVoiceChannelGrantUpdate pgvcgu = (PatchGroupVoiceChannelGrantUpdate)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiersPG1 = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - identifiersPG1.remove(IdentifierClass.USER); - identifiersPG1.update(mPatchGroupManager.update(pgvcgu.getPatchGroup1())); - - processChannelGrant(pgvcgu.getChannel1(), null, identifiersPG1, - tsbk.getOpcode(), pgvcgu.getTimestamp()); - - if(pgvcgu.hasPatchGroup2()) + if(mefc.isSupergroupCreate()) { - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiersPG2 = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - identifiersPG2.remove(IdentifierClass.USER); - identifiersPG2.update(mPatchGroupManager.update(pgvcgu.getPatchGroup2())); - - processChannelGrant(pgvcgu.getChannel2(), null, - identifiersPG2, tsbk.getOpcode(), pgvcgu.getTimestamp()); + mPatchGroupManager.addPatchGroup(mefc.getSuperGroup(), tsbk.getTimestamp()); + broadcastEvent(tsbk, DecodeEventType.COMMAND, "CREATE SUPERGROUP:" + mefc.getSuperGroup()); } - } - } - - private void processTSBKMotorolaOspPatchGroupChannelGrant(TSBKMessage tsbk) { - if(tsbk instanceof PatchGroupVoiceChannelGrant) - { - PatchGroupVoiceChannelGrant pgvcg = (PatchGroupVoiceChannelGrant)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - identifiers.remove(IdentifierClass.USER); - for(Identifier identifier : pgvcg.getIdentifiers()) + else if(mefc.isSupergroupCancel()) { - identifiers.update(mPatchGroupManager.update(identifier)); + mPatchGroupManager.removePatchGroup(mefc.getSuperGroup()); + broadcastEvent(tsbk, DecodeEventType.COMMAND, "CANCEL SUPERGROUP:" + mefc.getSuperGroup()); + } + else + { + broadcastEvent(tsbk, DecodeEventType.COMMAND, "FUNCTION CLASS: " + mefc.getFunctionClass() + " OPERAND:" + mefc.getFunctionOperand() + " ARGUMENTS:" + mefc.getFunctionArguments()); } - - processChannelGrant(pgvcg.getChannel(), pgvcg.getVoiceServiceOptions(), - identifiers, tsbk.getOpcode(), pgvcg.getTimestamp()); - } - } - - private void processTSBKTelephoneInterconnectVoiceChannelGrantUpdate(TSBKMessage tsbk) { - if(tsbk instanceof TelephoneInterconnectVoiceChannelGrantUpdate) - { - TelephoneInterconnectVoiceChannelGrantUpdate tivcgu = (TelephoneInterconnectVoiceChannelGrantUpdate)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiers = getMutableIdentifierCollection(tivcgu.getIdentifiers()); - - processChannelGrant(tivcgu.getChannel(), tivcgu.getVoiceServiceOptions(), - identifiers, tsbk.getOpcode(), tivcgu.getTimestamp()); - } - } - - private void processTSBKSndcpDataChannelGrant(TSBKMessage tsbk) { - if(tsbk instanceof SNDCPDataChannelGrant) - { - SNDCPDataChannelGrant dcg = (SNDCPDataChannelGrant)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiers = getMutableIdentifierCollection(dcg.getIdentifiers()); - - processChannelGrant(dcg.getChannel(), dcg.getServiceOptions(), - identifiers, tsbk.getOpcode(), dcg.getTimestamp()); } - } - private void processTSBKTelephoneInterconnectVoiceChannelGrant(TSBKMessage tsbk) { - if(tsbk instanceof TelephoneInterconnectVoiceChannelGrant) - { - TelephoneInterconnectVoiceChannelGrant tivcg = (TelephoneInterconnectVoiceChannelGrant)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiers = getMutableIdentifierCollection(tivcg.getIdentifiers()); - - processChannelGrant(tivcg.getChannel(), tivcg.getVoiceServiceOptions(), - identifiers, tsbk.getOpcode(), tivcg.getTimestamp()); - } } - private void processTSBKUnitToUnitVoiceChannelGrantUpdate(TSBKMessage tsbk) { - if(tsbk instanceof UnitToUnitVoiceChannelGrantUpdate) + private void processTSBKQueuedResponse(TSBKMessage tsbk) + { + if(tsbk instanceof QueuedResponse qr) { - UnitToUnitVoiceChannelGrantUpdate uuvcgu = (UnitToUnitVoiceChannelGrantUpdate)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiers = getMutableIdentifierCollection(uuvcgu.getIdentifiers()); - - processChannelGrant(uuvcgu.getChannel(), null, identifiers, - tsbk.getOpcode(), uuvcgu.getTimestamp()); + broadcastEvent(tsbk, DecodeEventType.RESPONSE, "QUEUED: " + + qr.getQueuedResponseServiceType().getDescription() + " REASON: " + qr.getQueuedResponseReason() + + " INFO: " + qr.getAdditionalInfo()); } - } - - private void processTSBKUnitToUnitVoiceChannelGrant(TSBKMessage tsbk) { - if(tsbk instanceof UnitToUnitVoiceChannelGrant) + else if(tsbk instanceof MotorolaQueuedResponse mqr) { - UnitToUnitVoiceChannelGrant uuvcg = (UnitToUnitVoiceChannelGrant)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiers = getMutableIdentifierCollection(uuvcg.getIdentifiers()); - - processChannelGrant(uuvcg.getChannel(), null, identifiers, - tsbk.getOpcode(), uuvcg.getTimestamp()); + broadcastEvent(tsbk, DecodeEventType.RESPONSE, "QUEUED: " + mqr.getQueuedServiceType().getDescription() + + " REASON: " + mqr.getQueuedResponseReason() + " INFO: " + mqr.getAdditionalInfo()); } } - private void processTSBKGroupVoiceChannelGrantUpdateExplicit(TSBKMessage tsbk) { - if(tsbk instanceof GroupVoiceChannelGrantUpdateExplicit) + private void processTSBKAcknowledgeResponse(TSBKMessage tsbk) + { + if(tsbk instanceof AcknowledgeResponse ar) { - GroupVoiceChannelGrantUpdateExplicit gvcgue = (GroupVoiceChannelGrantUpdateExplicit)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - identifiers.remove(IdentifierClass.USER); - identifiers.update(mPatchGroupManager.update(gvcgue.getGroupAddress())); - - processChannelGrant(gvcgue.getChannel(), gvcgue.getVoiceServiceOptions(), - identifiers, tsbk.getOpcode(), gvcgue.getTimestamp()); + broadcastEvent(tsbk, DecodeEventType.RESPONSE, "ACKNOWLEDGE " + ar.getAcknowledgedService().getDescription()); } - } - - private void processTSBKGroupVoiceChannelGrantUpdate(TSBKMessage tsbk) { - if(tsbk instanceof GroupVoiceChannelGrantUpdate) + else if(tsbk instanceof MotorolaAcknowledgeResponse mar) { - GroupVoiceChannelGrantUpdate gvcgu = (GroupVoiceChannelGrantUpdate)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiersA = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - identifiersA.remove(IdentifierClass.USER); - identifiersA.update(mPatchGroupManager.update(gvcgu.getGroupAddressA())); - - processChannelGrant(gvcgu.getChannelA(), null, identifiersA, - tsbk.getOpcode(), gvcgu.getTimestamp()); - - if(gvcgu.hasGroupB()) - { - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiersB = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - identifiersB.remove(IdentifierClass.USER); - identifiersB.update(mPatchGroupManager.update(gvcgu.getGroupAddressB())); - - processChannelGrant(gvcgu.getChannelB(), null, identifiersB, - tsbk.getOpcode(), gvcgu.getTimestamp()); - } - } - } - - private void processTSBKGroupVoiceChannelGrant(TSBKMessage tsbk) { - if(tsbk instanceof GroupVoiceChannelGrant) - { - GroupVoiceChannelGrant gvcg = (GroupVoiceChannelGrant)tsbk; - - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - identifiers.remove(IdentifierClass.USER); - for(Identifier identifier : gvcg.getIdentifiers()) - { - identifiers.update(mPatchGroupManager.update(identifier)); - } - - processChannelGrant(gvcg.getChannel(), gvcg.getVoiceServiceOptions(), - identifiers, tsbk.getOpcode(), gvcg.getTimestamp()); + broadcastEvent(tsbk, DecodeEventType.ACKNOWLEDGE, "ACKNOWLEDGE " + mar.getAcknowledgedService().getDescription()); } } - private void processTSBKDataChannelGrant(TSBKMessage tsbk) { - if(tsbk instanceof GroupDataChannelGrant) + /** + * TSBK Channel Grant Update messages + */ + private void processTSBKChannelGrantUpdate(TSBKMessage tsbk) + { + switch(tsbk.getOpcode()) { - GroupDataChannelGrant gdcg = (GroupDataChannelGrant)tsbk; + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE: + if(tsbk instanceof MotorolaGroupRegroupChannelUpdate pgvcgu) + { + processChannelUpdate(pgvcgu.getChannel1(), null, Collections.singletonList(pgvcgu.getPatchGroup1()), + tsbk.getOpcode(), pgvcgu.getTimestamp()); - //Make a copy of current identifiers and remove current user identifiers and replace from message - MutableIdentifierCollection identifiers = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - identifiers.remove(IdentifierClass.USER); - for(Identifier identifier : gdcg.getIdentifiers()) - { - identifiers.update(mPatchGroupManager.update(identifier)); - } + if(pgvcgu.hasPatchGroup2()) + { + processChannelUpdate(pgvcgu.getChannel2(), null, Collections.singletonList(pgvcgu.getPatchGroup2()), + tsbk.getOpcode(), pgvcgu.getTimestamp()); + } + } + break; + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE: + if(tsbk instanceof GroupVoiceChannelGrantUpdate gvcgu) + { + processChannelUpdate(gvcgu.getChannelA(), null, Collections.singletonList(gvcgu.getGroupAddressA()), + tsbk.getOpcode(), gvcgu.getTimestamp()); - processChannelGrant(gdcg.getChannel(), gdcg.getDataServiceOptions(), - identifiers, tsbk.getOpcode(), gdcg.getTimestamp()); + if(gvcgu.hasGroupB()) + { + processChannelGrant(gvcgu.getChannelB(), null, Collections.singletonList(gvcgu.getGroupAddressB()), + tsbk.getOpcode(), gvcgu.getTimestamp()); + } + } + break; + case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + if(tsbk instanceof GroupVoiceChannelGrantUpdateExplicit gvcgue) + { + processChannelUpdate(gvcgue.getChannel(), gvcgue.getServiceOptions(), gvcgue.getIdentifiers(), + tsbk.getOpcode(), gvcgue.getTimestamp()); + } + break; + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + if(tsbk instanceof TelephoneInterconnectVoiceChannelGrantUpdate tivcgu) + { + processChannelUpdate(tivcgu.getChannel(), tivcgu.getServiceOptions(), tivcgu.getIdentifiers(), + tsbk.getOpcode(), tivcgu.getTimestamp()); + } + break; + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + if(tsbk instanceof UnitToUnitVoiceChannelGrantUpdate uuvcgu) + { + processChannelUpdate(uuvcgu.getChannel(), null, uuvcgu.getIdentifiers(), tsbk.getOpcode(), + uuvcgu.getTimestamp()); + } + break; } } /** - * Processes encryption sync parameters carried by an LDU2 message - * - * @param esp that is non-null and valid + * TSBK Channel Grant messages */ - private void processEncryptionSyncParameters(EncryptionSyncParameters esp, long timestamp) + private void processTSBKChannelGrant(TSBKMessage tsbk) { - if(esp.isEncryptedAudio()) + switch(tsbk.getOpcode()) { - for(Identifier identifier : esp.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } - Encryption encryption = Encryption.fromValue(esp.getEncryptionKey().getValue().getAlgorithm()); - updateCurrentCall(DecodeEventType.CALL_ENCRYPTED, "ALGORITHM:" + encryption.toString(), timestamp); - } - else - { - getIdentifierCollection().remove(Form.ENCRYPTION_KEY); - updateCurrentCall(DecodeEventType.CALL, null, timestamp); + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT: + if(tsbk instanceof MotorolaGroupRegroupChannelGrant mgrcg) + { + processChannelGrant(mgrcg.getChannel(), mgrcg.getServiceOptions(), mgrcg.getIdentifiers(), tsbk.getOpcode(), + mgrcg.getTimestamp()); + } + break; + case OSP_GROUP_DATA_CHANNEL_GRANT: + if(tsbk instanceof GroupDataChannelGrant gdcg) + { + processChannelGrant(gdcg.getChannel(), gdcg.getDataServiceOptions(), gdcg.getIdentifiers(), + tsbk.getOpcode(), gdcg.getTimestamp()); + } + break; + case OSP_GROUP_VOICE_CHANNEL_GRANT: + if(tsbk instanceof GroupVoiceChannelGrant gvcg) + { + processChannelGrant(gvcg.getChannel(), gvcg.getServiceOptions(), gvcg.getIdentifiers(), tsbk.getOpcode(), + gvcg.getTimestamp()); + } + break; + case OSP_SNDCP_DATA_CHANNEL_GRANT: + if(tsbk instanceof SNDCPDataChannelGrant dcg) + { + processChannelGrant(dcg.getChannel(), dcg.getServiceOptions(), dcg.getIdentifiers(), tsbk.getOpcode(), + dcg.getTimestamp()); + } + break; + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + if(tsbk instanceof UnitToUnitVoiceChannelGrant uuvcg) + { + processChannelGrant(uuvcg.getChannel(), null, uuvcg.getIdentifiers(), tsbk.getOpcode(), + uuvcg.getTimestamp()); + } + break; + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + if(tsbk instanceof TelephoneInterconnectVoiceChannelGrant tivcg) + { + processChannelGrant(tivcg.getChannel(), tivcg.getServiceOptions(), tivcg.getIdentifiers(), tsbk.getOpcode(), + tivcg.getTimestamp()); + } + break; } } /** * Processes a Link Control Word (LCW) that is carried by either an LDU1 or a TDULC message. * + * Note: this method does not broadcast a DecoderStateEvent -- that is handled by the parent message processing + * method. + * * @param lcw that is non-null and valid */ - private void processLinkControl(LinkControlWord lcw, long timestamp) + private void processLC(LinkControlWord lcw, long timestamp, boolean isTerminator) { switch(lcw.getOpcode()) { //Calls in-progress on this channel case GROUP_VOICE_CHANNEL_USER: - case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER: - case MOTOROLA_TALK_COMPLETE: + case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER: case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: case UNIT_TO_UNIT_VOICE_CHANNEL_USER: - for(Identifier identifier : lcw.getIdentifiers()) + case UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + else { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); + processLCChannelUser(lcw, timestamp); + } + break; + case MOTOROLA_TALK_COMPLETE: + if(lcw instanceof LCMotorolaTalkComplete tc) + { + getIdentifierCollection().update(tc.getAddress()); + mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), tc.getAddress(), timestamp); + closeCurrentCallEvent(timestamp); } break; //Call termination case CALL_TERMINATION_OR_CANCELLATION: + closeCurrentCallEvent(timestamp); + //Note: we only broadcast an END state if this is a network-commanded channel teardown - if(lcw instanceof LCCallTermination && ((LCCallTermination)lcw).isNetworkCommandedTeardown()) + if(lcw instanceof LCCallTermination lcct && lcct.isNetworkCommandedTeardown()) { broadcast(new DecoderStateEvent(this, Event.END, State.FADE)); } @@ -1822,113 +1804,359 @@ private void processLinkControl(LinkControlWord lcw, long timestamp) //Calls in-progress on another channel case GROUP_VOICE_CHANNEL_UPDATE: + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + + if(lcw instanceof LCGroupVoiceChannelUpdate vcu) + { + MutableIdentifierCollection mic = getMutableIdentifierCollection(vcu.getGroupAddressA(), timestamp); + mTrafficChannelManager.processP1ChannelUpdate(vcu.getChannelA(), null, mic, + null, timestamp); + + if(vcu.hasChannelB()) + { + MutableIdentifierCollection micB = getMutableIdentifierCollection(vcu.getGroupAddressB(), timestamp); + mTrafficChannelManager.processP1ChannelUpdate(vcu.getChannelB(), null, micB, + null, timestamp); + } + } + break; case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + + if(lcw instanceof LCGroupVoiceChannelUpdateExplicit vcu) + { + MutableIdentifierCollection mic = getMutableIdentifierCollection(vcu.getGroupAddress(), timestamp); + mTrafficChannelManager.processP1ChannelUpdate(vcu.getChannel(), vcu.getServiceOptions(), mic, + null, timestamp); + } break; //Network configuration messages - case ADJACENT_SITE_STATUS_BROADCAST: - case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: + case RFSS_STATUS_BROADCAST: + if((getCurrentChannel() == null || getCurrentChannel().getDownlinkFrequency() > 0) && + mChannel.isStandardChannel() && lcw instanceof LCRFSSStatusBroadcast sb && + sb.getChannel().getDownlinkFrequency() > 0) + { + setCurrentChannel(sb.getChannel()); + DecoderLogicalChannelNameIdentifier channelID = + DecoderLogicalChannelNameIdentifier.create(sb.getChannel().toString(), Protocol.APCO25); + getIdentifierCollection().update(channelID); + setCurrentFrequency(sb.getChannel().getDownlinkFrequency()); + FrequencyConfigurationIdentifier frequencyID = FrequencyConfigurationIdentifier + .create(sb.getChannel().getDownlinkFrequency()); + getIdentifierCollection().update(frequencyID); + } + + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + + mNetworkConfigurationMonitor.process(lcw); + break; + case RFSS_STATUS_BROADCAST_EXPLICIT: + if((getCurrentChannel() == null || getCurrentChannel().getDownlinkFrequency() > 0) && + mChannel.isStandardChannel() && lcw instanceof LCRFSSStatusBroadcastExplicit sb && + sb.getChannel().getDownlinkFrequency() > 0) + { + setCurrentChannel(sb.getChannel()); + DecoderLogicalChannelNameIdentifier channelID = + DecoderLogicalChannelNameIdentifier.create(sb.getChannel().toString(), Protocol.APCO25); + getIdentifierCollection().update(channelID); + setCurrentFrequency(sb.getChannel().getDownlinkFrequency()); + FrequencyConfigurationIdentifier frequencyID = FrequencyConfigurationIdentifier + .create(sb.getChannel().getDownlinkFrequency()); + getIdentifierCollection().update(frequencyID); + } + + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + + mNetworkConfigurationMonitor.process(lcw); + break; + case NETWORK_STATUS_BROADCAST: + if((getCurrentChannel() == null || getCurrentChannel().getDownlinkFrequency() > 0) && + mChannel.isStandardChannel() && lcw instanceof LCNetworkStatusBroadcast sb && + sb.getChannel().getDownlinkFrequency() > 0) + { + setCurrentChannel(sb.getChannel()); + DecoderLogicalChannelNameIdentifier channelID = + DecoderLogicalChannelNameIdentifier.create(sb.getChannel().toString(), Protocol.APCO25); + getIdentifierCollection().update(channelID); + setCurrentFrequency(sb.getChannel().getDownlinkFrequency()); + FrequencyConfigurationIdentifier frequencyID = FrequencyConfigurationIdentifier + .create(sb.getChannel().getDownlinkFrequency()); + getIdentifierCollection().update(frequencyID); + } + + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + + mNetworkConfigurationMonitor.process(lcw); + break; case NETWORK_STATUS_BROADCAST_EXPLICIT: + if((getCurrentChannel() == null || getCurrentChannel().getDownlinkFrequency() > 0) && + mChannel.isStandardChannel() && lcw instanceof LCNetworkStatusBroadcastExplicit sb && + sb.getChannel().getDownlinkFrequency() > 0) + { + setCurrentChannel(sb.getChannel()); + DecoderLogicalChannelNameIdentifier channelID = + DecoderLogicalChannelNameIdentifier.create(sb.getChannel().toString(), Protocol.APCO25); + getIdentifierCollection().update(channelID); + setCurrentFrequency(sb.getChannel().getDownlinkFrequency()); + FrequencyConfigurationIdentifier frequencyID = FrequencyConfigurationIdentifier + .create(sb.getChannel().getDownlinkFrequency()); + getIdentifierCollection().update(frequencyID); + } + + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + + mNetworkConfigurationMonitor.process(lcw); + break; + + case ADJACENT_SITE_STATUS_BROADCAST: + case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: case PROTECTION_PARAMETER_BROADCAST: - case RFSS_STATUS_BROADCAST: - case RFSS_STATUS_BROADCAST_EXPLICIT: case SECONDARY_CONTROL_CHANNEL_BROADCAST: case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: case SYSTEM_SERVICE_BROADCAST: + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } mNetworkConfigurationMonitor.process(lcw); break; //Patch Group management - case MOTOROLA_PATCH_GROUP_ADD: - mPatchGroupManager.addPatchGroups(lcw.getIdentifiers()); + case MOTOROLA_GROUP_REGROUP_ADD: + mPatchGroupManager.addPatchGroups(lcw.getIdentifiers(), timestamp); break; - case MOTOROLA_PATCH_GROUP_DELETE: + case MOTOROLA_GROUP_REGROUP_DELETE: mPatchGroupManager.removePatchGroups(lcw.getIdentifiers()); break; - case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE: - mPatchGroupManager.addPatchGroups(lcw.getIdentifiers()); + case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE: + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + + if(lcw instanceof LCMotorolaGroupRegroupVoiceChannelUpdate vcu) + { + MutableIdentifierCollection mic = getMutableIdentifierCollection(vcu.getSupergroupAddress(), timestamp); + mTrafficChannelManager.processP1ChannelUpdate(vcu.getChannel(), vcu.getServiceOptions(), mic, + null, timestamp); + } + break; + case MOTOROLA_RADIO_REPROGRAM_HEADER: + case MOTOROLA_RADIO_REPROGRAM_RECORD: + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } break; //Other events case CALL_ALERT: - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.PAGE, "Call Alert"); + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.PAGE, "Call Alert"); break; case EXTENDED_FUNCTION_COMMAND: - if(lcw instanceof LCExtendedFunctionCommand) + if(isTerminator) { - LCExtendedFunctionCommand efc = (LCExtendedFunctionCommand)lcw; - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.COMMAND, - "Extended Function: " + efc.getExtendedFunction() + + closeCurrentCallEvent(timestamp); + } + if(lcw instanceof LCExtendedFunctionCommand efc) + { + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.COMMAND, "Function: " + + efc.getExtendedFunction() + " Arguments:" + efc.getExtendedFunctionArguments()); } break; + case EXTENDED_FUNCTION_COMMAND_EXTENDED: + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + if(lcw instanceof LCExtendedFunctionCommandExtended efce) + { + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.COMMAND, "Function: " + + efce.getExtendedFunction() + " Arguments:" + efce.getExtendedFunctionArguments()); + } case GROUP_AFFILIATION_QUERY: - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.QUERY, "Group Affiliation"); + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.QUERY, "Group Affiliation"); break; case MESSAGE_UPDATE: - if(lcw instanceof LCMessageUpdate) + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + if(lcw instanceof LCMessageUpdate mu) { - LCMessageUpdate mu = (LCMessageUpdate)lcw; - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.SDM, + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.SDM, "MSG:" + mu.getShortDataMessage()); } break; + case MESSAGE_UPDATE_EXTENDED: + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + if(lcw instanceof LCMessageUpdateExtended mue) + { + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.SDM, "MSG:" + + mue.getShortDataMessage()); + } + break; case STATUS_QUERY: - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.QUERY, "Status"); + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.QUERY, "Status"); break; case STATUS_UPDATE: - if(lcw instanceof LCStatusUpdate) + if(isTerminator) { - LCStatusUpdate su = (LCStatusUpdate)lcw; - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.STATUS, + closeCurrentCallEvent(timestamp); + } + if(lcw instanceof LCStatusUpdate su) + { + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.STATUS, "UNIT:" + su.getUnitStatus() + " USER:" + su.getUserStatus()); } break; + case STATUS_UPDATE_EXTENDED: + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + if(lcw instanceof LCStatusUpdateExtended sue) + { + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.STATUS, "UNIT:" + + sue.getUnitStatus() + " USER:" + sue.getUserStatus()); + } + break; case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - if(lcw instanceof LCTelephoneInterconnectAnswerRequest) + if(isTerminator) { - LCTelephoneInterconnectAnswerRequest tiar = (LCTelephoneInterconnectAnswerRequest)lcw; - - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.PAGE, "Telephone Call:" + tiar.getTelephoneNumber()); + closeCurrentCallEvent(timestamp); + } + if(lcw instanceof LCTelephoneInterconnectAnswerRequest tiar) + { + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.PAGE, "Telephone Call:" + + tiar.getTelephoneNumber()); } break; case UNIT_AUTHENTICATION_COMMAND: - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.COMMAND, "Authenticate Unit"); + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.COMMAND, "Authenticate Unit"); break; case UNIT_REGISTRATION_COMMAND: - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.COMMAND, "Unit Registration"); + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.COMMAND, "Unit Registration"); break; case UNIT_TO_UNIT_ANSWER_REQUEST: - processBroadcast(lcw.getIdentifiers(), timestamp, DecodeEventType.PAGE, "Unit-to-Unit Answer Request"); + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.PAGE, "Unit-to-Unit Answer Request"); + break; + case L3HARRIS_RETURN_TO_CONTROL_CHANNEL: + if(lcw instanceof LCHarrisReturnToControlChannel) + { + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.RESPONSE, "L3Harris Opcode 10 - Unknown"); + } + break; + case MOTOROLA_EMERGENCY_ALARM_ACTIVATION: + if(lcw instanceof LCMotorolaEmergencyAlarmActivation) + { + broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.EMERGENCY, "EMERGENCY ALARM ACTIVATION"); + } break; case MOTOROLA_UNIT_GPS: - if(lcw instanceof LCMotorolaUnitGPS moto) + if(lcw instanceof LCMotorolaUnitGPS gps) { - String gpsDetails = "LOCATION: " + moto.getLatitude() + " " + moto.getLongitude(); + mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), gps.getLocation(), timestamp); + MutableIdentifierCollection mic = getMutableIdentifierCollection(gps.getIdentifiers(), timestamp); PlottableDecodeEvent event = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, timestamp) - .location(new GeoPosition(moto.getLatitude(), moto.getLongitude())) + .location(gps.getGeoPosition()) .channel(getCurrentChannel()) - .details(gpsDetails) + .details(gps.getLocation().toString()) .end(timestamp) .protocol(Protocol.APCO25) - .identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers())) + .identifiers(mic) .build(); broadcast(event); } + + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + break; + case SOURCE_ID_EXTENSION: + //Ignore - handled elsewhere break; default: -// mLog.debug("Unrecognized LCW Opcode: " + lcw.getOpcode().name() + " VENDOR:" + lcw.getVendor() + -// " OPCODE:" + lcw.getOpcodeNumber()); + if(isTerminator) + { + closeCurrentCallEvent(timestamp); + } + LOGGING_SUPPRESSOR.info(lcw.getVendor().toString() + lcw.getOpcodeNumber() + lcw.getMessage().toHexString(), + 1, "Unrecognized LCW Opcode: " + lcw.getOpcode().name() + " VENDOR:" + lcw.getVendor() + + " OPCODE:" + lcw.getOpcodeNumber() + " MSG:" + lcw.getMessage().toHexString() + + " CHAN:" + getCurrentChannel() + " FREQ:" + getCurrentFrequency()); break; } } + /** + * Closes the call event on the current channel. + * @param timestamp + */ + private void closeCurrentCallEvent(long timestamp) + { + mTrafficChannelManager.closeP1CallEvent(getCurrentFrequency(), timestamp); + getIdentifierCollection().remove(IdentifierClass.USER); + } + @Override public String getActivitySummary() { - return mNetworkConfigurationMonitor.getActivitySummary(); + StringBuilder sb = new StringBuilder(); + sb.append(mNetworkConfigurationMonitor.getActivitySummary()); + sb.append("\n"); + sb.append(mPatchGroupManager.getPatchGroupSummary()); + return sb.toString(); } @Override @@ -1940,6 +2168,14 @@ public void receiveDecoderStateEvent(DecoderStateEvent event) resetState(); mNetworkConfigurationMonitor.reset(); break; + case NOTIFICATION_SOURCE_FREQUENCY: + long frequency = event.getFrequency(); + + //Notify the TCM that our control frequency has changed. + if(mChannel.isStandardChannel()) + { + mTrafficChannelManager.setCurrentControlFrequency(frequency, mChannel); + } default: break; } @@ -1952,7 +2188,7 @@ public void start() mPatchGroupManager.clear(); //Change the default (45-second) traffic channel timeout to 1 second - if(mChannelType == ChannelType.TRAFFIC) + if(mChannel.isTrafficChannel()) { broadcast(new ChangeChannelTimeoutEvent(this, ChannelType.TRAFFIC, 1000)); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java index a75f4743d..105be6a6c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageFramer.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -30,7 +30,6 @@ import io.github.dsheirer.dsp.symbol.Dibit; import io.github.dsheirer.dsp.symbol.ISyncDetectListener; import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.message.Message; import io.github.dsheirer.message.MessageProviderModule; import io.github.dsheirer.message.StuffBitsMessage; import io.github.dsheirer.message.SyncLossMessage; @@ -40,8 +39,8 @@ import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket; import io.github.dsheirer.module.decode.ip.udp.UDPPacket; import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; import io.github.dsheirer.module.decode.p25.phase1.message.P25MessageFactory; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUMessageFactory; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage; @@ -73,7 +72,7 @@ public class P25P1MessageFramer implements Listener, IP25P1DataUnitDetect private P25P1DataUnitDetector mDataUnitDetector; private P25P1ChannelStatusProcessor mChannelStatusProcessor = new P25P1ChannelStatusProcessor(); - private Listener mMessageListener; + private Listener mMessageListener; private boolean mAssemblingMessage = false; private CorrectedBinaryMessage mBinaryMessage; private P25P1DataUnitID mDataUnitID; @@ -153,7 +152,7 @@ private void updateBitsProcessed(int bitsProcessed) * * @param messageListener to receive framed and decoded messages */ - public void setListener(Listener messageListener) + public void setListener(Listener messageListener) { mMessageListener = messageListener; } @@ -346,7 +345,7 @@ else if(mDataUnitID == P25P1DataUnitID.TRUNKING_SIGNALING_BLOCK_2) } break; default: - P25Message message = P25MessageFactory.create(mDataUnitID, mNAC, getTimestamp(), mBinaryMessage); + P25P1Message message = P25MessageFactory.create(mDataUnitID, mNAC, getTimestamp(), mBinaryMessage); mMessageListener.receive(message); reset(mDataUnitID.getMessageLength()); break; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java index 1ab280fd1..52061f57b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -20,44 +20,81 @@ import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.ExtendedSourceLinkControlWord; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension; +import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU1Message; +import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage; import io.github.dsheirer.sample.Listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.List; import java.util.Map; import java.util.TreeMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public class P25P1MessageProcessor implements Listener +/** + * APCO25 Phase 1 Message Processor. + * + * Performs post-message creation processing before the message is sent downstream. + */ +public class P25P1MessageProcessor implements Listener { private final static Logger mLog = LoggerFactory.getLogger(P25P1MessageProcessor.class); + /** + * Downstream message listener + */ private Listener mMessageListener; - /* Map of up to 16 band identifiers per RFSS. These identifier update - * messages are inserted into any message that conveys channel information - * so that the uplink/downlink frequencies can be calculated */ + /** + * Map of up to 16 band identifiers per RFSS. These identifier update messages are inserted into any message that + * conveys channel information so that the uplink/downlink frequencies can be calculated + */ private Map mFrequencyBandMap = new TreeMap(); + /** + * Temporary holding of an extended source link control message while it awaits the extension message to arrive. + */ + private ExtendedSourceLinkControlWord mExtendedSourceLinkControlWord; + public P25P1MessageProcessor() { } + /** + * Preloads frequency band information from the control channel. + * @param content to preload + */ + public void preload(P25FrequencyBandPreloadDataContent content) + { + if(content.hasData()) + { + for(IFrequencyBand frequencyBand: content.getData()) + { + mFrequencyBandMap.put(frequencyBand.getIdentifier(), frequencyBand); + } + } + } + @Override - public void receive(Message message) + public void receive(IMessage message) { - /** - * Capture frequency band identifier messages and inject them into any - * messages that require band information in order to calculate the - * up-link and down-link frequencies for any numeric channel references - * contained within the message. - */ if(message.isValid()) { - /* Insert frequency band identifier update messages into channel-type messages */ + //Reassemble extended source link control messages. + if(message instanceof LDU1Message ldu) + { + reassembleLC(ldu.getLinkControlWord()); + } + else if(message instanceof TDULinkControlMessage tdu) + { + reassembleLC(tdu.getLinkControlWord()); + } + + //Insert frequency band identifier update messages into channel-type messages */ if(message instanceof IFrequencyBandReceiver) { IFrequencyBandReceiver receiver = (IFrequencyBandReceiver)message; @@ -78,8 +115,7 @@ public void receive(Message message) } } - /* Store band identifiers so that they can be injected into channel - * type messages */ + //Store band identifiers so that they can be injected into channel type messages if(message instanceof IFrequencyBand) { IFrequencyBand bandIdentifier = (IFrequencyBand)message; @@ -93,6 +129,30 @@ public void receive(Message message) } } + /** + * Processes link control words to reassemble source ID extension messages. + * @param linkControlWord to process + */ + private void reassembleLC(LinkControlWord linkControlWord) + { + if(linkControlWord instanceof ExtendedSourceLinkControlWord eslcw) + { + mExtendedSourceLinkControlWord = eslcw; + } + else if(linkControlWord instanceof LCSourceIDExtension sie && mExtendedSourceLinkControlWord != null) + { + mExtendedSourceLinkControlWord.setSourceIDExtension(sie); + mExtendedSourceLinkControlWord = null; + } + else + { + //The source extension message should always immediately follow the message that is being extended, so if + //we get a message that is not an extended message or the extension, then we've missed the extension and we + //should nullify any extended message that's waiting for the extension. + mExtendedSourceLinkControlWord = null; + } + } + public void dispose() { mFrequencyBandMap.clear(); 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 2739c957c..8e0254558 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 @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -45,16 +45,15 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SecondaryControlChannelBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SecondaryControlChannelBroadcastExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SystemServiceBroadcast; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Tracks the network configuration details of a P25 Phase 1 network from the broadcast messages @@ -236,7 +235,7 @@ public void process(LinkControlWord lcw) } break; case CHANNEL_IDENTIFIER_UPDATE: - case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: + case CHANNEL_IDENTIFIER_UPDATE_VU: if(lcw instanceof IFrequencyBand) { IFrequencyBand band = (IFrequencyBand)lcw; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25FrequencyBand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25FrequencyBand.java new file mode 100644 index 000000000..7623c4dd8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25FrequencyBand.java @@ -0,0 +1,95 @@ +package io.github.dsheirer.module.decode.p25.phase1.message; + +import org.apache.commons.math3.util.FastMath; + +/** + * Utility class to declare a frequency band + */ +public class P25FrequencyBand implements IFrequencyBand +{ + private int mIdentifier; + private long mBaseFrequency; + private long mTransmitOffset; + private long mChannelSpacing; + private int mBandwidth; + private int mTimeslotCount; + + /** + * Construct an instance + * @param identifier for this frequency band + * @param baseFrequency in Hertz + * @param transmitOffset in Hertz + * @param channelSpacing in Hertz + * @param bandwidth in Hertz + * @param timeslotCount for number of timeslots, 1 or 2 + */ + public P25FrequencyBand(int identifier, long baseFrequency, long transmitOffset, long channelSpacing, + int bandwidth, int timeslotCount) + { + mIdentifier = identifier; + mChannelSpacing = channelSpacing; + mBaseFrequency = baseFrequency; + mBandwidth = bandwidth; + mTransmitOffset = transmitOffset; + mTimeslotCount = timeslotCount; + } + + @Override + public int getIdentifier() + { + return mIdentifier; + } + + @Override + public long getChannelSpacing() + { + return mChannelSpacing; + } + + @Override + public long getBaseFrequency() + { + return mBaseFrequency; + } + + @Override + public int getBandwidth() + { + return mBandwidth; + } + + @Override + public long getTransmitOffset() + { + return mTransmitOffset; + } + + @Override + public long getDownlinkFrequency(int channelNumber) + { + if(isTDMA()) + { + return getBaseFrequency() + (getChannelSpacing() * (int)(FastMath.floor(channelNumber / getTimeslotCount()))); + } + + return getBaseFrequency() + (getChannelSpacing() * channelNumber); + } + + @Override + public long getUplinkFrequency(int channelNumber) + { + return getDownlinkFrequency(channelNumber) + getTransmitOffset(); + } + + @Override + public boolean isTDMA() + { + return getTimeslotCount() > 1; + } + + @Override + public int getTimeslotCount() + { + return mTimeslotCount; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25MessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25MessageFactory.java index 6958bd615..fe61a8fb4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25MessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25MessageFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -49,7 +49,7 @@ public class P25MessageFactory * correction * @return constructed message parser */ - public static P25Message create(P25P1DataUnitID dataUnitID, int nac, long timestamp, CorrectedBinaryMessage message) + public static P25P1Message create(P25P1DataUnitID dataUnitID, int nac, long timestamp, CorrectedBinaryMessage message) { switch(dataUnitID) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25P1Message.java similarity index 68% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25Message.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25P1Message.java index 8628075be..8a2e91320 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25P1Message.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -20,25 +20,32 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.message.Message; +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.message.TimeslotMessage; import io.github.dsheirer.module.decode.p25.identifier.APCO25Nac; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.protocol.Protocol; -public abstract class P25Message extends Message +/** + * Base APCO25 Phase 1 Message + */ +public abstract class P25P1Message extends TimeslotMessage implements IMessage { - public enum DuplexMode - { - HALF, FULL - } + //P25 Phase 1 ICD defines octets starting at zero. + public static final int OCTET_0_BIT_0 = 0; + public static final int OCTET_1_BIT_8 = 8; + public static final int OCTET_2_BIT_16 = 16; + public static final int OCTET_3_BIT_24 = 24; + public static final int OCTET_4_BIT_32 = 32; + public static final int OCTET_5_BIT_40 = 40; + public static final int OCTET_6_BIT_48 = 48; + public static final int OCTET_7_BIT_56 = 56; + public static final int OCTET_8_BIT_64 = 64; + public static final int OCTET_9_BIT_72 = 72; + public static final int OCTET_10_BIT_80 = 80; + public static final int OCTET_11_BIT_88 = 88; + public static final int OCTET_12_BIT_96 = 96; - public enum SessionMode - { - PACKET, CIRCUIT - } - - private CorrectedBinaryMessage mMessage; - private boolean mValid = true; private Identifier mNAC; /** @@ -48,10 +55,9 @@ public enum SessionMode * @param nac Network Access Code (NAC) for the message * @param timestamp when the message was transmitted */ - public P25Message(CorrectedBinaryMessage message, int nac, long timestamp) + public P25P1Message(CorrectedBinaryMessage message, int nac, long timestamp) { - super(timestamp); - mMessage = message; + super(message, 0, timestamp); mNAC = APCO25Nac.create(nac); } @@ -61,7 +67,7 @@ public P25Message(CorrectedBinaryMessage message, int nac, long timestamp) * @param message containing the binary message and optional corrected bit count * @param nac Network Access Code (NAC) for the message */ - public P25Message(CorrectedBinaryMessage message, int nac) + public P25P1Message(CorrectedBinaryMessage message, int nac) { this(message, nac, System.currentTimeMillis()); } @@ -71,28 +77,15 @@ public P25Message(CorrectedBinaryMessage message, int nac) * @param nac Network Access Code (NAC) for the message * @param timestamp when the message was transmitted */ - protected P25Message(int nac, long timestamp) + protected P25P1Message(int nac, long timestamp) { - super(timestamp); + super(null, 0, timestamp); mNAC = APCO25Nac.create(nac); } /** - * Establishes the message binary sequence. + * NAC code for this message */ - protected void setMessage(CorrectedBinaryMessage message) - { - mMessage = message; - } - - /** - * The transmitted binary message - */ - public CorrectedBinaryMessage getMessage() - { - return mMessage; - } - public Identifier getNAC() { return mNAC; @@ -125,21 +118,4 @@ public Protocol getProtocol() { return Protocol.APCO25; } - - /** - * Indicates if this message is valid and has passed all error detection and correction routines. - */ - @Override - public boolean isValid() - { - return mValid; - } - - /** - * Sets this message to the valid/invalid state. The default state is valid (true). - */ - protected void setValid(boolean valid) - { - mValid = valid; - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/UnknownP25Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/UnknownP25Message.java index f58f1051b..f1ebfe3cf 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/UnknownP25Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/UnknownP25Message.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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; @@ -23,20 +22,19 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; - import java.util.Collections; import java.util.List; /** * P25 Message with unknown or unrecognized data unit id */ -public class UnknownP25Message extends P25Message +public class UnknownP25Message extends P25P1Message { private P25P1DataUnitID mDataUnitID; public UnknownP25Message(CorrectedBinaryMessage message, int nac, long timestamp, P25P1DataUnitID dataUnitID) { - super(message, nac); + super(message, nac, timestamp); mDataUnitID = dataUnitID; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25OtherMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25OtherMessageFilter.java index d130370e5..3dca8af56 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25OtherMessageFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25OtherMessageFilter.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -22,7 +22,7 @@ import io.github.dsheirer.filter.Filter; import io.github.dsheirer.filter.FilterElement; import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import java.util.function.Function; /** @@ -51,7 +51,7 @@ public Function getKeyExtractor() @Override public boolean canProcess(IMessage message) { - return message instanceof P25Message && super.canProcess(message); + return message instanceof P25P1Message && super.canProcess(message); } /** @@ -62,7 +62,7 @@ private class KeyExtractor implements Function @Override public String apply(IMessage message) { - if(message instanceof P25Message) + if(message instanceof P25P1Message) { return OTHER_KEY; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25P1MessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25P1MessageFilterSet.java index faa5f842c..0b1598305 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25P1MessageFilterSet.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/P25P1MessageFilterSet.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -23,7 +23,7 @@ import io.github.dsheirer.filter.SyncLossMessageFilter; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.SyncLossMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; /** * Filter set for P25 messages @@ -53,6 +53,6 @@ public P25P1MessageFilterSet() @Override public boolean canProcess(IMessage message) { - return message instanceof P25Message || message instanceof SyncLossMessage; + return message instanceof P25P1Message || message instanceof SyncLossMessage; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HDUMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HDUMessage.java index bae93fe81..ccce03547 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HDUMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HDUMessage.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.hdu; @@ -27,20 +24,19 @@ import io.github.dsheirer.edac.ReedSolomon_63_47_17_P25; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; - -public class HDUMessage extends P25Message +public class HDUMessage extends P25P1Message { private final static Logger mLog = LoggerFactory.getLogger(HDUMessage.class); private static final int[] GOLAY_WORD_STARTS = {0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198, 216, 234, 252, 270, 288, 306, 324, 342, 360, 278, 396, 414, 432, 450, 468, 486, 504, 522, 540, 558, 576, 594, 612, 630}; - private static final int[] CW_HEX_0 = {0, 1, 2, 3, 4, 5, 6}; + private static final int[] CW_HEX_0 = {0, 1, 2, 3, 4, 5}; private static final int[] CW_HEX_1 = {18, 19, 20, 21, 22, 23}; private static final int[] CW_HEX_2 = {36, 37, 38, 39, 40, 41}; private static final int[] CW_HEX_3 = {54, 55, 56, 57, 58, 59}; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/ExtendedSourceLinkControlWord.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/ExtendedSourceLinkControlWord.java new file mode 100644 index 000000000..c61c1b91e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/ExtendedSourceLinkControlWord.java @@ -0,0 +1,88 @@ +/* + * ***************************************************************************** + * 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.lc; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension; + +/** + * Extended link control word. Base class for adding in the extension word for messages that require two link control + * fragments to fit the content. + */ +public abstract class ExtendedSourceLinkControlWord extends LinkControlWord +{ + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); + private LCSourceIDExtension mSourceIDExtension; + private FullyQualifiedRadioIdentifier mSourceAddress; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public ExtendedSourceLinkControlWord(CorrectedBinaryMessage message) + { + super(message); + } + + /** + * Optional source ID extension message for a fully qualified radio source. + */ + protected LCSourceIDExtension getSourceIDExtension() + { + return mSourceIDExtension; + } + + /** + * Sets the extension message. + */ + public void setSourceIDExtension(LCSourceIDExtension extension) + { + mSourceIDExtension = extension; + } + + /** + * Indicates if this message is carrying a source ID extension message. + */ + protected boolean hasSourceIDExtension() + { + return mSourceIDExtension != null; + } + + /** + * Source address + */ + public FullyQualifiedRadioIdentifier getSourceAddress() + { + if(mSourceAddress == null && hasSourceIDExtension()) + { + int radio = getInt(SOURCE_ADDRESS); + int wacn = getSourceIDExtension().getWACN(); + int system = getSourceIDExtension().getSystem(); + int id = getSourceIDExtension().getId(); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(radio, wacn, system, id); + } + + return mSourceAddress; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java index 3a30f8331..ec629aacb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -36,8 +36,8 @@ public enum LinkControlOpcode TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER("TELEPHONE INTERCONNECT VOICE CHANNEL USER", 6), TELEPHONE_INTERCONNECT_ANSWER_REQUEST("TELEPHONE INTERCONNECT ANSWER REQUEST", 7), RESERVED_08("RESERVED-08", 8), - RESERVED_09("RESERVED-09", 9), - RESERVED_0A("RESERVED-0A", 10), + SOURCE_ID_EXTENSION("SOURCE ID EXTENSION", 9), + UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED("UNIT-TO-UNIT VOICE CHANNEL USER EXTENDED", 10), RESERVED_0B("RESERVED-0B", 11), RESERVED_0C("RESERVED-0C", 12), RESERVED_0D("RESERVED-0D", 13), @@ -52,10 +52,10 @@ public enum LinkControlOpcode CALL_ALERT("CALL ALERT", 22), EXTENDED_FUNCTION_COMMAND("EXTENDED FUNCTION COMMAND", 23), CHANNEL_IDENTIFIER_UPDATE("CHANNEL IDENTIFIER UPDATE", 24), - CHANNEL_IDENTIFIER_UPDATE_EXPLICIT("CHANNEL IDENTIFIER UPDATE EXPLICIT", 25), - RESERVED_1A("RESERVED-1A", 26), - RESERVED_1B("RESERVED-1B", 27), - RESERVED_1C("RESERVED-1C", 28), + CHANNEL_IDENTIFIER_UPDATE_VU("CHANNEL IDENTIFIER UPDATE EXPLICIT", 25), + STATUS_UPDATE_EXTENDED("STATUS UPDATE-SOURCE ID REQUIRED", 26), + MESSAGE_UPDATE_EXTENDED("MESSAGE UPDATE-SOURCE ID REQUIRED", 27), + EXTENDED_FUNCTION_COMMAND_EXTENDED("EXTENDED FUNCTION COMMAND-SOURCE ID REQUIRED", 28), RESERVED_1D("RESERVED-1D", 29), RESERVED_1E("RESERVED-1E", 30), RESERVED_1F("RESERVED-1F", 31), @@ -69,7 +69,7 @@ public enum LinkControlOpcode ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT("ADJACENT SITE STATUS EXPLICIT", 39), RFSS_STATUS_BROADCAST_EXPLICIT("RFSS STATUS BROADCAST EXPLICIT", 40), NETWORK_STATUS_BROADCAST_EXPLICIT("NETWORK STATUS BROADCAST EXPLICIT", 41), - RESERVED_2A("RESERVED-2A", 42), + CONVENTIONAL_FALLBACK_INDICATION("CONVENTIONAL FALLBACK INDICATION", 42), RESERVED_2B("RESERVED-2B", 43), RESERVED_2C("RESERVED-2C", 44), RESERVED_2D("RESERVED-2D", 45), @@ -92,15 +92,18 @@ public enum LinkControlOpcode RESERVED_3E("RESERVED-3E", 62), RESERVED_3F("RESERVED-3F", 63), - MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER("PATCH GROUP VOICE CHANNEL USER", 0), - MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE("PATCH GROUP VOICE CHANNEL UPDATE", 1), - MOTOROLA_PATCH_GROUP_ADD("PATCH GROUP ADD", 3), - MOTOROLA_PATCH_GROUP_DELETE("PATCH GROUP DELETE", 4), - MOTOROLA_UNIT_GPS("UNIT GPS", 6), - MOTOROLA_TALK_COMPLETE("TALK_COMPLETE", 15), + MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER("MOTOROLA GROUP REGROUP VOICE CHANNEL USER", 0), + MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE("MOTOROLA GROUP REGROUP VOICE CHANNEL UPDATE", 1), + MOTOROLA_GROUP_REGROUP_ADD("MOTOROLA GROUP REGROUP ADD", 3), + MOTOROLA_GROUP_REGROUP_DELETE("MOTOROLA GROUP REGROUP DELETE", 4), + MOTOROLA_UNIT_GPS("MOTOROLA UNIT GPS", 6), + MOTOROLA_EMERGENCY_ALARM_ACTIVATION("MOTOROLA EMERGENCY ALARM ACTIVATION", 10), + MOTOROLA_TALK_COMPLETE("MOTOROLA TALK_COMPLETE", 15), + MOTOROLA_RADIO_REPROGRAM_HEADER("MOTOROLA RADIO REPROGRAM HEADER", 21), + MOTOROLA_RADIO_REPROGRAM_RECORD("MOTOROLA RADIO REPROGRAM CONTINUATION", 23), MOTOROLA_UNKNOWN("MOTOROLA UNKNOWN", -1), - L3HARRIS_UNKNOWN_0A("UNKNOWN OPCODE 10", 10), + L3HARRIS_RETURN_TO_CONTROL_CHANNEL("UNKNOWN OPCODE 10", 10), L3HARRIS_UNKNOWN_2A("UNKNOWN OPCODE 42", 42), L3HARRIS_UNKNOWN_2B("UNKNOWN OPCODE 43", 43), L3HARRIS_UNKNOWN("L3HARRIS UNKNOWN", -1), @@ -133,19 +136,19 @@ public enum LinkControlOpcode * Motorola Opcodes */ public static final EnumSet MOTOROLA_OPCODES = - EnumSet.range(MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER, MOTOROLA_UNKNOWN); + EnumSet.range(MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER, MOTOROLA_UNKNOWN); /** * L3Harris Opcodes */ - public static final EnumSet L3HARRIS_OPCODES = EnumSet.of(L3HARRIS_UNKNOWN_0A, + public static final EnumSet L3HARRIS_OPCODES = EnumSet.of(L3HARRIS_RETURN_TO_CONTROL_CHANNEL, L3HARRIS_UNKNOWN_2A, L3HARRIS_UNKNOWN_2B, L3HARRIS_UNKNOWN); /** * Network/channel related opcodes */ public static final EnumSet NETWORK_OPCODES = EnumSet.of(CHANNEL_IDENTIFIER_UPDATE, - CHANNEL_IDENTIFIER_UPDATE_EXPLICIT, SYSTEM_SERVICE_BROADCAST, SECONDARY_CONTROL_CHANNEL_BROADCAST, + CHANNEL_IDENTIFIER_UPDATE_VU, SYSTEM_SERVICE_BROADCAST, SECONDARY_CONTROL_CHANNEL_BROADCAST, SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT, ADJACENT_SITE_STATUS_BROADCAST, ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT, RFSS_STATUS_BROADCAST, RFSS_STATUS_BROADCAST_EXPLICIT, NETWORK_STATUS_BROADCAST, NETWORK_STATUS_BROADCAST_EXPLICIT, PROTECTION_PARAMETER_BROADCAST); @@ -205,7 +208,7 @@ public static LinkControlOpcode fromValue(int value, Vendor vendor) switch(value) { case 10: - return L3HARRIS_UNKNOWN_0A; + return L3HARRIS_RETURN_TO_CONTROL_CHANNEL; case 42: return L3HARRIS_UNKNOWN_2A; case 43: @@ -217,17 +220,23 @@ public static LinkControlOpcode fromValue(int value, Vendor vendor) switch(value) { case 0: - return MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER; + return MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER; case 1: - return MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE; + return MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE; case 3: - return MOTOROLA_PATCH_GROUP_ADD; + return MOTOROLA_GROUP_REGROUP_ADD; case 4: - return MOTOROLA_PATCH_GROUP_DELETE; + return MOTOROLA_GROUP_REGROUP_DELETE; case 6: return MOTOROLA_UNIT_GPS; + case 10: + return MOTOROLA_EMERGENCY_ALARM_ACTIVATION; case 15: return MOTOROLA_TALK_COMPLETE; + case 21: + return MOTOROLA_RADIO_REPROGRAM_HEADER; + case 23: + return MOTOROLA_RADIO_REPROGRAM_RECORD; default: return MOTOROLA_UNKNOWN; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWord.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWord.java index 3da7c17d1..23f4a8dbe 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWord.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWord.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,66 +14,73 @@ * * 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.lc; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.FragmentedIntField; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.AbstractMessage; import io.github.dsheirer.module.decode.p25.reference.Vendor; - import java.util.List; /** * APCO 25 Link Control Word. This message word is contained in Logical Link Data Unit 1 and Terminator with * Link Control messages. */ -public abstract class LinkControlWord +public abstract class LinkControlWord extends AbstractMessage { + public static final int OCTET_0_BIT_0 = 0; + public static final int OCTET_1_BIT_8 = 8; + public static final int OCTET_2_BIT_16 = 16; + public static final int OCTET_3_BIT_24 = 24; + public static final int OCTET_4_BIT_32 = 32; + public static final int OCTET_5_BIT_40 = 40; + public static final int OCTET_6_BIT_48 = 48; + public static final int OCTET_7_BIT_56 = 56; + public static final int OCTET_8_BIT_64 = 64; + public static final int OCTET_9_BIT_72 = 72; + public static final int OCTET_10_BIT_80 = 80; + public static final int OCTET_11_BIT_88 = 88; + public static final int OCTET_12_BIT_96 = 96; + private static final int ENCRYPTION_FLAG = 0; private static final int STANDARD_VENDOR_ID_FLAG = 1; - private static final int[] OPCODE = {2, 3, 4, 5, 6, 7}; - private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15}; - - private BinaryMessage mMessage; + private static final FragmentedIntField OPCODE = FragmentedIntField.of(2, 3, 4, 5, 6, 7); + private static final IntField VENDOR = IntField.length8(OCTET_1_BIT_8); private LinkControlOpcode mLinkControlOpcode; - private boolean mValid = true; + private boolean mValid; /** * Constructs a Link Control Word from the binary message sequence. * * @param message */ - public LinkControlWord(BinaryMessage message) + public LinkControlWord(CorrectedBinaryMessage message) { - mMessage = message; + super(message); } /** - * Binary message sequence for this LCW + * Indicates if the message passes CRC checks. */ - protected BinaryMessage getMessage() + public boolean isValid() { - return mMessage; + return mValid; } /** - * Sets the valid flag for this message to mark a flag as invalid (false) or the default value is true. + * Sets the valid flag for this message. */ public void setValid(boolean valid) { mValid = valid; } - /** - * Indicates if this link control word is valid and has passed all error detection and correction routines. - */ - public boolean isValid() - { - return mValid; - } - /** * Indicates if this is an encrypted LCW */ @@ -110,7 +116,7 @@ public Vendor getVendor() /** * Lookup the Vendor format for the specified LCW */ - public static Vendor getVendor(BinaryMessage binaryMessage) + public static Vendor getVendor(CorrectedBinaryMessage binaryMessage) { if(isStandardVendorFormat(binaryMessage)) { @@ -140,13 +146,13 @@ public LinkControlOpcode getOpcode() */ public int getOpcodeNumber() { - return getMessage().getInt(OPCODE); + return getInt(OPCODE); } /** * Identifies the link control word opcode from the binary message. */ - public static LinkControlOpcode getOpcode(BinaryMessage binaryMessage) + public static LinkControlOpcode getOpcode(CorrectedBinaryMessage binaryMessage) { return LinkControlOpcode.fromValue(binaryMessage.getInt(OPCODE), getVendor(binaryMessage)); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWordFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWordFactory.java index d127bca39..1ac522a39 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWordFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWordFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -19,14 +19,17 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode10; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisReturnToControlChannel; import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode42; import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode43; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupAdd; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupDelete; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUpdate; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaEmergencyAlarmActivation; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupGroupDelete; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupAdd; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaRadioReprogramHeader; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaRadioReprogramRecord; import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkComplete; import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnitGPS; import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnknownOpcode; @@ -34,14 +37,17 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCAdjacentSiteStatusBroadcastExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCCallAlert; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCCallTermination; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCChannelIdentifierUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCChannelIdentifierUpdateVU; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCConventionalFallback; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCExtendedFunctionCommand; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCFrequencyBandUpdate; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCFrequencyBandUpdateExplicit; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCExtendedFunctionCommandExtended; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupAffiliationQuery; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdate; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdateExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUser; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCMessageUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCMessageUpdateExtended; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCNetworkStatusBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCNetworkStatusBroadcastExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCProtectionParameterBroadcast; @@ -49,8 +55,10 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCRFSSStatusBroadcastExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSecondaryControlChannelBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSecondaryControlChannelBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusQuery; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusUpdateExtended; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSystemServiceBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectAnswerRequest; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectVoiceChannelUser; @@ -58,6 +66,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitRegistrationCommand; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitToUnitAnswerRequest; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitToUnitVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitToUnitVoiceChannelUserExtended; /** * Factory class for creating link control word (LCW) message parsers. @@ -67,96 +76,114 @@ public class LinkControlWordFactory /** * Creates a link control word from the binary message sequence. * - * @param binaryMessage containing the LCW binary message sequence. + * @param message containing the LCW binary message sequence. */ - public static LinkControlWord create(BinaryMessage binaryMessage) + public static LinkControlWord create(CorrectedBinaryMessage message) { - LinkControlOpcode opcode = LinkControlWord.getOpcode(binaryMessage); + LinkControlOpcode opcode = LinkControlWord.getOpcode(message); switch(opcode) { case ADJACENT_SITE_STATUS_BROADCAST: - return new LCAdjacentSiteStatusBroadcast(binaryMessage); + return new LCAdjacentSiteStatusBroadcast(message); case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: - return new LCAdjacentSiteStatusBroadcastExplicit(binaryMessage); + return new LCAdjacentSiteStatusBroadcastExplicit(message); case CALL_ALERT: - return new LCCallAlert(binaryMessage); + return new LCCallAlert(message); case CALL_TERMINATION_OR_CANCELLATION: - return new LCCallTermination(binaryMessage); + return new LCCallTermination(message); case CHANNEL_IDENTIFIER_UPDATE: - return new LCFrequencyBandUpdate(binaryMessage); - case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: - return new LCFrequencyBandUpdateExplicit(binaryMessage); + return new LCChannelIdentifierUpdate(message); + case CHANNEL_IDENTIFIER_UPDATE_VU: + return new LCChannelIdentifierUpdateVU(message); + case CONVENTIONAL_FALLBACK_INDICATION: + return new LCConventionalFallback(message); case EXTENDED_FUNCTION_COMMAND: - return new LCExtendedFunctionCommand(binaryMessage); + return new LCExtendedFunctionCommand(message); + case EXTENDED_FUNCTION_COMMAND_EXTENDED: + return new LCExtendedFunctionCommandExtended(message); case GROUP_AFFILIATION_QUERY: - return new LCGroupAffiliationQuery(binaryMessage); + return new LCGroupAffiliationQuery(message); case GROUP_VOICE_CHANNEL_USER: - return new LCGroupVoiceChannelUser(binaryMessage); + return new LCGroupVoiceChannelUser(message); case GROUP_VOICE_CHANNEL_UPDATE: - return new LCGroupVoiceChannelUpdate(binaryMessage); + return new LCGroupVoiceChannelUpdate(message); case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: - return new LCGroupVoiceChannelUpdateExplicit(binaryMessage); + return new LCGroupVoiceChannelUpdateExplicit(message); case MESSAGE_UPDATE: - return new LCMessageUpdate(binaryMessage); + return new LCMessageUpdate(message); + case MESSAGE_UPDATE_EXTENDED: + return new LCMessageUpdateExtended(message); case NETWORK_STATUS_BROADCAST: - return new LCNetworkStatusBroadcast(binaryMessage); + return new LCNetworkStatusBroadcast(message); case NETWORK_STATUS_BROADCAST_EXPLICIT: - return new LCNetworkStatusBroadcastExplicit(binaryMessage); + return new LCNetworkStatusBroadcastExplicit(message); case PROTECTION_PARAMETER_BROADCAST: - return new LCProtectionParameterBroadcast(binaryMessage); + return new LCProtectionParameterBroadcast(message); case RFSS_STATUS_BROADCAST: - return new LCRFSSStatusBroadcast(binaryMessage); + return new LCRFSSStatusBroadcast(message); case RFSS_STATUS_BROADCAST_EXPLICIT: - return new LCRFSSStatusBroadcastExplicit(binaryMessage); + return new LCRFSSStatusBroadcastExplicit(message); case SECONDARY_CONTROL_CHANNEL_BROADCAST: - return new LCSecondaryControlChannelBroadcast(binaryMessage); + return new LCSecondaryControlChannelBroadcast(message); case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - return new LCSecondaryControlChannelBroadcastExplicit(binaryMessage); + return new LCSecondaryControlChannelBroadcastExplicit(message); + case SOURCE_ID_EXTENSION: + return new LCSourceIDExtension(message); case STATUS_QUERY: - return new LCStatusQuery(binaryMessage); + return new LCStatusQuery(message); case STATUS_UPDATE: - return new LCStatusUpdate(binaryMessage); + return new LCStatusUpdate(message); + case STATUS_UPDATE_EXTENDED: + return new LCStatusUpdateExtended(message); case SYSTEM_SERVICE_BROADCAST: - return new LCSystemServiceBroadcast(binaryMessage); + return new LCSystemServiceBroadcast(message); case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - return new LCTelephoneInterconnectAnswerRequest(binaryMessage); + return new LCTelephoneInterconnectAnswerRequest(message); case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: - return new LCTelephoneInterconnectVoiceChannelUser(binaryMessage); + return new LCTelephoneInterconnectVoiceChannelUser(message); case UNIT_AUTHENTICATION_COMMAND: - return new LCUnitAuthenticationCommand(binaryMessage); + return new LCUnitAuthenticationCommand(message); case UNIT_REGISTRATION_COMMAND: - return new LCUnitRegistrationCommand(binaryMessage); + return new LCUnitRegistrationCommand(message); case UNIT_TO_UNIT_ANSWER_REQUEST: - return new LCUnitToUnitAnswerRequest(binaryMessage); + return new LCUnitToUnitAnswerRequest(message); case UNIT_TO_UNIT_VOICE_CHANNEL_USER: - return new LCUnitToUnitVoiceChannelUser(binaryMessage); + return new LCUnitToUnitVoiceChannelUser(message); + case UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: + return new LCUnitToUnitVoiceChannelUserExtended(message); - case L3HARRIS_UNKNOWN_0A: - return new LCHarrisUnknownOpcode10(binaryMessage); + case L3HARRIS_RETURN_TO_CONTROL_CHANNEL: + return new LCHarrisReturnToControlChannel(message); case L3HARRIS_UNKNOWN_2A: - return new LCHarrisUnknownOpcode42(binaryMessage); + return new LCHarrisUnknownOpcode42(message); case L3HARRIS_UNKNOWN_2B: - return new LCHarrisUnknownOpcode43(binaryMessage); + return new LCHarrisUnknownOpcode43(message); case L3HARRIS_UNKNOWN: - return new UnknownLinkControlWord(binaryMessage); + return new UnknownLinkControlWord(message); - case MOTOROLA_PATCH_GROUP_ADD: - return new LCMotorolaPatchGroupAdd(binaryMessage); - case MOTOROLA_PATCH_GROUP_DELETE: - return new LCMotorolaPatchGroupDelete(binaryMessage); - case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER: - return new LCMotorolaPatchGroupVoiceChannelUser(binaryMessage); + case MOTOROLA_GROUP_REGROUP_ADD: + return new LCMotorolaGroupRegroupAdd(message); + case MOTOROLA_GROUP_REGROUP_DELETE: + return new LCMotorolaGroupGroupDelete(message); + case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER: + return new LCMotorolaGroupRegroupVoiceChannelUser(message); case MOTOROLA_TALK_COMPLETE: - return new LCMotorolaTalkComplete(binaryMessage); - case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE: - return new LCMotorolaPatchGroupVoiceChannelUpdate(binaryMessage); + return new LCMotorolaTalkComplete(message); + case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE: + return new LCMotorolaGroupRegroupVoiceChannelUpdate(message); case MOTOROLA_UNIT_GPS: - return new LCMotorolaUnitGPS(binaryMessage); + return new LCMotorolaUnitGPS(message); + case MOTOROLA_RADIO_REPROGRAM_HEADER: + return new LCMotorolaRadioReprogramHeader(message); + case MOTOROLA_RADIO_REPROGRAM_RECORD: + return new LCMotorolaRadioReprogramRecord(message); + case MOTOROLA_EMERGENCY_ALARM_ACTIVATION: + return new LCMotorolaEmergencyAlarmActivation(message); case MOTOROLA_UNKNOWN: - return new LCMotorolaUnknownOpcode(binaryMessage); + return new LCMotorolaUnknownOpcode(message); default: - return new UnknownLinkControlWord(binaryMessage); + return new UnknownLinkControlWord(message); } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/UnknownLinkControlWord.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/UnknownLinkControlWord.java index 9d5547ba9..2a82591b2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/UnknownLinkControlWord.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/UnknownLinkControlWord.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,14 +14,13 @@ * * 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.lc; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; - import java.util.Collections; import java.util.List; @@ -36,7 +34,7 @@ public class UnknownLinkControlWord extends LinkControlWord * * @param message */ - public UnknownLinkControlWord(BinaryMessage message) + public UnknownLinkControlWord(CorrectedBinaryMessage message) { super(message); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/VoiceLinkControlMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/VoiceLinkControlMessage.java new file mode 100644 index 000000000..4c6ed8529 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/VoiceLinkControlMessage.java @@ -0,0 +1,57 @@ +/* + * ***************************************************************************** + * 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.lc; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +/** + * Base voice call link control word + */ +public abstract class VoiceLinkControlMessage extends LinkControlWord implements IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_2_BIT_16); + private VoiceServiceOptions mVoiceServiceOptions; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public VoiceLinkControlMessage(CorrectedBinaryMessage message) + { + super(message); + } + + /** + * Service Options for this channel + */ + public VoiceServiceOptions getServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode10.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisReturnToControlChannel.java similarity index 54% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode10.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisReturnToControlChannel.java index 5c6b71d5c..f76038534 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode10.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisReturnToControlChannel.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -19,7 +19,8 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; @@ -28,20 +29,30 @@ import java.util.List; /** - * L3Harris Unknown Opcode 10 (0x0A) + * L3Harris Opcode 10 (0x0A) - This seems to be a 'return to control channel' or 'private data call paging'. *

- * Observed in a traffic channel carried by a TDU during an SNDCP packet data session. The controller send an 'All - * blocks received' PDU to the radio in the middle of a stream of these TDU/LC messages and the traffic channel ended - * with Call Termination TDULC messages. So, this may be some form of current data session user information update. + * Observed on a phase 1 traffic data channel carried by a TDU during an SNDCP packet data session. The controller sent + * an 'All blocks received' PDU to the radio and an 'Activate TDS context' in the middle of a stream of these TDU/LC + * messages, and then the radio terminated the traffic channel. So, this may be some form of return to control + * channel or maybe a private data call paging where the mobile returns to the control channel to receive the private + * data channel grant for another data call + * + * When the radio returned to the control channel, control sent the following two MAC messages on the Phase 2 control: + * LOCCH-U NAC:9/x009 SIGNAL CUSTOM/UNKNOWN VENDOR:HARRIS ID:A4 OPCODE:160 LENGTH:9 MSG:A0A409AC0312014871 (radio 0x014871 go to data channel 0x0312??) + * LOCCH-U NAC:9/x009 SIGNAL CUSTOM/UNKNOWN VENDOR:HARRIS ID:A4 OPCODE:172 LENGTH:12 MSG:ACA40C000312014871980418 (from 0x014871 to 0x980418 unit-2-unit data channel grant?) + * + * Both messages seem to refer to channel 0-786 (0x0312) so this may be a unit-2-unit private Phase 1 call or maybe + * a private data call. Radio addresses: 0x014871 and 0x980418 + * + * In a second observation (Rockwall, TX), the control channel used the SNDCP data channel grant to send the + * mobile to the data channel and then the data channel transmitted a sequence of TDULCs containing only this message. + * */ -public class LCHarrisUnknownOpcode10 extends LinkControlWord +public class LCHarrisReturnToControlChannel extends LinkControlWord { - private static final int[] UNKNOWN_1 = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] TARGET_RADIO = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_RADIO = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, - 66, 67, 68, 69, 70, 71}; - + private static final IntField UNKNOWN_1 = IntField.length8(OCTET_2_BIT_16); + private static final IntField SOURCE_RADIO = IntField.length24(OCTET_3_BIT_24); + private static final IntField TARGET_RADIO = IntField.length24(OCTET_6_BIT_48); private RadioIdentifier mSourceRadio; private RadioIdentifier mTargetRadio; private List mIdentifiers; @@ -51,7 +62,7 @@ public class LCHarrisUnknownOpcode10 extends LinkControlWord * * @param message */ - public LCHarrisUnknownOpcode10(BinaryMessage message) + public LCHarrisReturnToControlChannel(CorrectedBinaryMessage message) { super(message); } @@ -71,7 +82,7 @@ public String toString() } else { - sb.append("L3HARRIS UNKNOWN OPCODE 10 "); + sb.append("L3HARRIS RETURN TO CONTROL CHANNEL OR PRIVATE DATA CALL PAGING"); sb.append(" FM:").append(getSourceRadio()); sb.append(" TO:").append(getTargetRadio()); sb.append(" UNK:").append(getUnknown()); @@ -82,7 +93,7 @@ public String toString() public String getUnknown() { - return getMessage().getHex(UNKNOWN_1, 2); + return getMessage().getHex(UNKNOWN_1); } /** @@ -92,7 +103,7 @@ public RadioIdentifier getSourceRadio() { if(mSourceRadio == null) { - mSourceRadio = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_RADIO)); + mSourceRadio = APCO25RadioIdentifier.createFrom(getInt(SOURCE_RADIO)); } return mSourceRadio; @@ -105,7 +116,7 @@ public RadioIdentifier getTargetRadio() { if(mTargetRadio == null) { - mTargetRadio = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_RADIO)); + mTargetRadio = APCO25RadioIdentifier.createTo(getInt(TARGET_RADIO)); } return mTargetRadio; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode42.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode42.java index 7d9c93199..2bb4bb9b9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode42.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode42.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -19,7 +19,7 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import java.util.Collections; @@ -38,7 +38,7 @@ public class LCHarrisUnknownOpcode42 extends LinkControlWord * * @param message */ - public LCHarrisUnknownOpcode42(BinaryMessage message) + public LCHarrisUnknownOpcode42(CorrectedBinaryMessage message) { super(message); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode43.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode43.java index 9703ccfa4..d9edcdff3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode43.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode43.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -19,7 +19,7 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import java.util.Collections; @@ -38,7 +38,7 @@ public class LCHarrisUnknownOpcode43 extends LinkControlWord * * @param message */ - public LCHarrisUnknownOpcode43(BinaryMessage message) + public LCHarrisUnknownOpcode43(CorrectedBinaryMessage message) { super(message); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaEmergencyAlarmActivation.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaEmergencyAlarmActivation.java new file mode 100644 index 000000000..f0864e231 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaEmergencyAlarmActivation.java @@ -0,0 +1,134 @@ +/* + * ***************************************************************************** + * 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.lc.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Link Control Opcode 10 appears to be an Emergency Alarm Activation message. + * + * This was observed in the following sequence: + * 1. Radio registers on network. + * 2. Radio assigned group and affiliation group + * 3. Network acknowledges radio's emergency alarm request + * 4. Network sends this message, interspersed with the ack message (3) + * 5. Network grants emergency group voice channel to radio and the same talkgroup references in this message, which + * is also the assigned talkgroup during initial registration. + * 6. Network grants second non-emerg group voice channel to radio and a second (unknown 0x39) talkgroup that may be + * a supervisor talkgroup. + * + * This link control message was also transmitted on active traffic channels. + * + * Note: the same opcode is used on both on control channel TSBK and traffic channel LCW messaging. + */ +public class LCMotorolaEmergencyAlarmActivation extends LinkControlWord +{ + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_2_BIT_16); + //There seems to be room for another group address here, octets 4/5?? + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); + private TalkgroupIdentifier mGroupAddress; + private RadioIdentifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCMotorolaEmergencyAlarmActivation(CorrectedBinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("**CRC-FAILED** "); + } + + if(isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + else + { + sb.append("MOTOROLA EMERGENCY ALARM ACTIVATION RADIO:").append(getSourceAddress()); + sb.append(" TALKGROUP:").append(getGroupAddress()); + sb.append(" MSG:").append(getMessage().toHexString()); + } + + return sb.toString(); + } + + /** + * Source Address that activated the emergency alarm. + */ + public RadioIdentifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createTo(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * Talkgroup to be activated for the emergency voice call + */ + public TalkgroupIdentifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + /** + * List of identifiers contained in this message + */ + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupGroupDelete.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupGroupDelete.java new file mode 100644 index 000000000..145033231 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupGroupDelete.java @@ -0,0 +1,144 @@ +/* + * ***************************************************************************** + * 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.lc.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Group Regroup Delete + */ +public class LCMotorolaGroupGroupDelete extends LinkControlWord +{ + private static final IntField SUPERGROUP = IntField.length16(OCTET_2_BIT_16); + private static final IntField PATCHED_GROUP_1 = IntField.length16(OCTET_4_BIT_32); + private static final IntField PATCHED_GROUP_2 = IntField.length16(OCTET_6_BIT_48); + + private APCO25PatchGroup mSuperGroup; + private TalkgroupIdentifier mPatchedGroup1; + private TalkgroupIdentifier mPatchedGroup2; + private List mIdentifiers; + + public LCMotorolaGroupGroupDelete(CorrectedBinaryMessage message) + { + super(message); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA GROUP REGROUP DELETE SUPERGROUP:").append(getSuperGroup()); + return sb.toString(); + } + + /** + * Patch Group + */ + public Identifier getSuperGroup() + { + if(mSuperGroup == null) + { + PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getInt(SUPERGROUP))); + patchGroup.addPatchedTalkgroups(getPatchedGroups()); + mSuperGroup = APCO25PatchGroup.create(patchGroup); + } + + return mSuperGroup; + } + + public List getPatchedGroups() + { + List patchedGroups = new ArrayList<>(); + + if(hasPatchedGroup1()) + { + patchedGroups.add(getPatchedGroup1()); + } + + if(hasPatchedGroup2()) + { + patchedGroups.add(getPatchedGroup2()); + } + + return patchedGroups; + } + + /** + * Patched Group 1 + */ + public TalkgroupIdentifier getPatchedGroup1() + { + if(mPatchedGroup1 == null) + { + mPatchedGroup1 = APCO25Talkgroup.create(getInt(PATCHED_GROUP_1)); + } + + return mPatchedGroup1; + } + + public boolean hasPatchedGroup1() + { + return getInt(PATCHED_GROUP_1) != 0 && + (getInt(SUPERGROUP) != getInt(PATCHED_GROUP_1)); + } + + /** + * Patched Group 2 + */ + public TalkgroupIdentifier getPatchedGroup2() + { + if(mPatchedGroup2 == null) + { + mPatchedGroup2 = APCO25Talkgroup.create(getInt(PATCHED_GROUP_2)); + } + + return mPatchedGroup2; + } + + public boolean hasPatchedGroup2() + { + return getInt(PATCHED_GROUP_2) != 0 && + (getInt(SUPERGROUP) != getInt(PATCHED_GROUP_2)); + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSuperGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupAdd.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupRegroupAdd.java similarity index 53% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupAdd.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupRegroupAdd.java index 628ee5215..9a56c5a14 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupAdd.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupRegroupAdd.java @@ -1,49 +1,50 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.motorola; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.patch.PatchGroup; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; - +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import java.util.ArrayList; import java.util.List; -public class LCMotorolaPatchGroupAdd extends MotorolaLinkControlWord +/** + * Motorola Group Regroup Add + */ +public class LCMotorolaGroupRegroupAdd extends LinkControlWord { - private static final int[] PATCH_GROUP = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] PATCHED_GROUP_1 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] PATCHED_GROUP_2 = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final IntField SUPERGROUP = IntField.length16(OCTET_2_BIT_16); + private static final IntField PATCHED_GROUP_1 = IntField.length16(OCTET_4_BIT_32); + private static final IntField PATCHED_GROUP_2 = IntField.length16(OCTET_6_BIT_48); private APCO25PatchGroup mPatchGroup; private TalkgroupIdentifier mPatchedGroup1; private TalkgroupIdentifier mPatchedGroup2; private List mIdentifiers; - public LCMotorolaPatchGroupAdd(BinaryMessage message) + public LCMotorolaGroupRegroupAdd(CorrectedBinaryMessage message) { super(message); } @@ -52,9 +53,7 @@ public LCMotorolaPatchGroupAdd(BinaryMessage message) public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("MOTOROLA ADD PATCH GROUP:").append(getPatchGroup()); - sb.append(" MSG:").append(getMessage().toHexString()); - + sb.append("MOTOROLA GROUP REGROUP ADD SUPERGROUP:").append(getPatchGroup()); return sb.toString(); } @@ -65,7 +64,7 @@ public Identifier getPatchGroup() { if(mPatchGroup == null) { - PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getMessage().getInt(PATCH_GROUP))); + PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getInt(SUPERGROUP))); patchGroup.addPatchedTalkgroups(getPatchedGroups()); mPatchGroup = APCO25PatchGroup.create(patchGroup); } @@ -97,7 +96,7 @@ public TalkgroupIdentifier getPatchedGroup1() { if(mPatchedGroup1 == null) { - mPatchedGroup1 = APCO25Talkgroup.create(getMessage().getInt(PATCHED_GROUP_1)); + mPatchedGroup1 = APCO25Talkgroup.create(getInt(PATCHED_GROUP_1)); } return mPatchedGroup1; @@ -105,8 +104,8 @@ public TalkgroupIdentifier getPatchedGroup1() public boolean hasPatchedGroup1() { - return getMessage().getInt(PATCHED_GROUP_1) != 0 && - (getMessage().getInt(PATCH_GROUP) != getMessage().getInt(PATCHED_GROUP_1)); + return getInt(PATCHED_GROUP_1) != 0 && + (getInt(SUPERGROUP) != getInt(PATCHED_GROUP_1)); } /** @@ -116,7 +115,7 @@ public TalkgroupIdentifier getPatchedGroup2() { if(mPatchedGroup2 == null) { - mPatchedGroup2 = APCO25Talkgroup.create(getMessage().getInt(PATCHED_GROUP_2)); + mPatchedGroup2 = APCO25Talkgroup.create(getInt(PATCHED_GROUP_2)); } return mPatchedGroup2; @@ -124,8 +123,7 @@ public TalkgroupIdentifier getPatchedGroup2() public boolean hasPatchedGroup2() { - return getMessage().getInt(PATCHED_GROUP_2) != 0 && - (getMessage().getInt(PATCH_GROUP) != getMessage().getInt(PATCHED_GROUP_2)); + return getInt(PATCHED_GROUP_2) != 0 && (getInt(SUPERGROUP) != getInt(PATCHED_GROUP_2)); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupRegroupVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupRegroupVoiceChannelUpdate.java new file mode 100644 index 000000000..595da312a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupRegroupVoiceChannelUpdate.java @@ -0,0 +1,103 @@ +/* + * ***************************************************************************** + * 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.lc.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.VoiceLinkControlMessage; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Group Regroup Voice Channel Update + */ +public class LCMotorolaGroupRegroupVoiceChannelUpdate extends VoiceLinkControlMessage implements IFrequencyBandReceiver +{ + private static final IntField SUPER_GROUP = IntField.length16(OCTET_3_BIT_24); + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_56); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_7_BIT_56 + 4); + private APCO25PatchGroup mSupergroupAddress; + private APCO25Channel mChannel; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCMotorolaGroupRegroupVoiceChannelUpdate(CorrectedBinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA GROUP REGROUP VOICE CHANNEL UPDATE"); + sb.append(" SUPERGROUP:").append(getSupergroupAddress()); + sb.append(" CHANNEL:").append(getChannel()); + return sb.toString(); + } + + public APCO25PatchGroup getSupergroupAddress() + { + if(mSupergroupAddress == null) + { + PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getInt(SUPER_GROUP))); + mSupergroupAddress = APCO25PatchGroup.create(patchGroup); + } + + return mSupergroupAddress; + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = Collections.singletonList(getSupergroupAddress()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupRegroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupRegroupVoiceChannelUser.java new file mode 100644 index 000000000..36b454664 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaGroupRegroupVoiceChannelUser.java @@ -0,0 +1,103 @@ +/* + * ***************************************************************************** + * 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.lc.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.VoiceLinkControlMessage; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Group Regroup Voice Channel User. + */ +public class LCMotorolaGroupRegroupVoiceChannelUser extends VoiceLinkControlMessage +{ + private static final IntField SUPERGROUP_ADDRESS = IntField.length16(OCTET_4_BIT_32); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); + + private APCO25PatchGroup mSupergroupAddress; + private Identifier mSourceAddress; + private List mIdentifiers; + + public LCMotorolaGroupRegroupVoiceChannelUser(CorrectedBinaryMessage message) + { + super(message); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA GROUP REGROUP VOICE CHANNEL USER FM:").append(getSourceAddress()); + sb.append(" TO:").append(getSupergroupAddress()); + sb.append(" ").append(getServiceOptions()); + + return sb.toString(); + } + + /** + * Supergroup (ie Patch group) address + */ + public APCO25PatchGroup getSupergroupAddress() + { + if(mSupergroupAddress == null) + { + PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getInt(SUPERGROUP_ADDRESS))); + mSupergroupAddress = APCO25PatchGroup.create(patchGroup); + } + + return mSupergroupAddress; + } + + /** + * Source address + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSupergroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupDelete.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupDelete.java deleted file mode 100644 index 341391d8d..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupDelete.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.lc.motorola; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.identifier.patch.PatchGroup; -import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; - -import java.util.ArrayList; -import java.util.List; - -public class LCMotorolaPatchGroupDelete extends MotorolaLinkControlWord -{ - private static final int[] PATCH_GROUP = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] PATCHED_GROUP_1 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] PATCHED_GROUP_2 = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - - private APCO25PatchGroup mPatchGroup; - private TalkgroupIdentifier mPatchedGroup1; - private TalkgroupIdentifier mPatchedGroup2; - private List mIdentifiers; - - public LCMotorolaPatchGroupDelete(BinaryMessage message) - { - super(message); - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append("MOTOROLA DELETE PATCH GROUP:").append(getPatchGroup()); - sb.append(" MSG:").append(getMessage().toHexString()); - - return sb.toString(); - } - - /** - * Patch Group - */ - public Identifier getPatchGroup() - { - if(mPatchGroup == null) - { - PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getMessage().getInt(PATCH_GROUP))); - patchGroup.addPatchedTalkgroups(getPatchedGroups()); - mPatchGroup = APCO25PatchGroup.create(patchGroup); - } - - return mPatchGroup; - } - - public List getPatchedGroups() - { - List patchedGroups = new ArrayList<>(); - - if(hasPatchedGroup1()) - { - patchedGroups.add(getPatchedGroup1()); - } - - if(hasPatchedGroup2()) - { - patchedGroups.add(getPatchedGroup2()); - } - - return patchedGroups; - } - - /** - * Patched Group 1 - */ - public TalkgroupIdentifier getPatchedGroup1() - { - if(mPatchedGroup1 == null) - { - mPatchedGroup1 = APCO25Talkgroup.create(getMessage().getInt(PATCHED_GROUP_1)); - } - - return mPatchedGroup1; - } - - public boolean hasPatchedGroup1() - { - return getMessage().getInt(PATCHED_GROUP_1) != 0 && - (getMessage().getInt(PATCH_GROUP) != getMessage().getInt(PATCHED_GROUP_1)); - } - - /** - * Patched Group 2 - */ - public TalkgroupIdentifier getPatchedGroup2() - { - if(mPatchedGroup2 == null) - { - mPatchedGroup2 = APCO25Talkgroup.create(getMessage().getInt(PATCHED_GROUP_2)); - } - - return mPatchedGroup2; - } - - public boolean hasPatchedGroup2() - { - return getMessage().getInt(PATCHED_GROUP_2) != 0 && - (getMessage().getInt(PATCH_GROUP) != getMessage().getInt(PATCHED_GROUP_2)); - } - - /** - * List of identifiers contained in this message - */ - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getPatchGroup()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUpdate.java deleted file mode 100644 index 592db8a8d..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUpdate.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.lc.motorola; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.identifier.patch.PatchGroup; -import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; - -import java.util.ArrayList; -import java.util.List; - -/** - * Motorola Patch Group Voice Channel Update - */ -public class LCMotorolaPatchGroupVoiceChannelUpdate extends MotorolaLinkControlWord implements IFrequencyBandReceiver -{ - private static final int[] UNKNOWN_1 = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] PATCH_GROUP = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] UNKNOWN_2 = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] UNKNOWN_3 = {48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] FREQUENCY_BAND = {56, 57, 58, 59}; - private static final int[] CHANNEL_NUMBER = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - - private APCO25PatchGroup mPatchGroup; - private IChannelDescriptor mChannel; - private List mIdentifiers; - private List mChannels; - - /** - * Constructs a Link Control Word from the binary message sequence. - * - * @param message - */ - public LCMotorolaPatchGroupVoiceChannelUpdate(BinaryMessage message) - { - super(message); - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append("MOTOROLA PATCH VOICE CHANNEL UPDATE"); - sb.append(" PATCH GROUP:").append(getPatchGroup()); - sb.append(" CHANNEL:").append(getChannel()); - sb.append(" UNK1:").append(getUnknownField1()); - sb.append(" UNK2:").append(getUnknownField2()); - sb.append(" UNK3:").append(getUnknownField3()); - sb.append(" MSG:").append(getMessage().toHexString()); - return sb.toString(); - } - - public APCO25PatchGroup getPatchGroup() - { - if(mPatchGroup == null) - { - PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getMessage().getInt(PATCH_GROUP))); - mPatchGroup = APCO25PatchGroup.create(patchGroup); - } - - return mPatchGroup; - } - - public IChannelDescriptor getChannel() - { - if(mChannel == null) - { - mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), getMessage().getInt(CHANNEL_NUMBER)); - } - - return mChannel; - } - - public String getUnknownField1() - { - return getMessage().getHex(UNKNOWN_1, 2); - } - public String getUnknownField2() - { - return getMessage().getHex(UNKNOWN_2, 2); - } - public String getUnknownField3() - { - return getMessage().getHex(UNKNOWN_3, 2); - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getPatchGroup()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - if(mChannels == null) - { - mChannels = new ArrayList<>(); - mChannels.add(getChannel()); - } - - return mChannels; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUser.java deleted file mode 100644 index 1b59d4e9c..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupVoiceChannelUser.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.lc.motorola; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.identifier.patch.PatchGroup; -import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - -import java.util.ArrayList; -import java.util.List; - -public class LCMotorolaPatchGroupVoiceChannelUser extends MotorolaLinkControlWord -{ - private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] PATCH_GROUP_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - - private VoiceServiceOptions mVoiceServiceOptions; - private APCO25PatchGroup mGroupAddress; - private Identifier mSourceAddress; - private List mIdentifiers; - - public LCMotorolaPatchGroupVoiceChannelUser(BinaryMessage message) - { - super(message); - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append("MOTOROLA PATCH GROUP VOICE CHANNEL USER FM:").append(getSourceAddress()); - sb.append(" TO:").append(getGroupAddress()); - sb.append(" ").append(getVoiceServiceOptions()); - sb.append(" MSG:").append(getMessage().toHexString()); - - return sb.toString(); - } - - /** - * Service Options for this channel - */ - public VoiceServiceOptions getVoiceServiceOptions() - { - if(mVoiceServiceOptions == null) - { - mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); - } - - return mVoiceServiceOptions; - } - - /** - * Talkgroup address - */ - public APCO25PatchGroup getGroupAddress() - { - if(mGroupAddress == null) - { - PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getMessage().getInt(PATCH_GROUP_ADDRESS))); - mGroupAddress = APCO25PatchGroup.create(patchGroup); - } - - return mGroupAddress; - } - - /** - * Source address - */ - public Identifier getSourceAddress() - { - if(mSourceAddress == null) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS)); - } - - return mSourceAddress; - } - - /** - * List of identifiers contained in this message - */ - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getGroupAddress()); - mIdentifiers.add(getSourceAddress()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaRadioReprogramHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaRadioReprogramHeader.java new file mode 100644 index 000000000..48d4886fa --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaRadioReprogramHeader.java @@ -0,0 +1,155 @@ +/* + * ***************************************************************************** + * 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.lc.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Link Control Opcode 0x15. + * + * Note: I suspect this is some form of radio reprogramming message, possibly used to update encryption. + * + * I've seen LCOpcode 0x15 and 0x17 twice and in both cases it was sent in a TDULC at the end of a call for the same + * radio on the CNYICC system. The header references the talkgroup that the radio is calling, so it may be some form + * of dynamic regrouping, but the total content is quite large. + * + * Note: this seems to only be targeted to certain radios since it wasn't sent at the end of every call. + * + * Examples observed on traffic channels in TDULC at the end of a call following the Motorola Talk Complete message + * for Radio: 15,104,082 (0xE67852) and TG:7101 (0x1BBD) + * Example 1 + * + * 1590 1BBD 07 0100 F 8BA / 1BBD=TG, 07=record count, 0100=?, F=sequence number, 8BA=crc checksum + * 1790 01 F BEE00 2AE E67 / BEE00=WACN, 2AE=SYS, E67...= RADIO ID + * 1790 02 F 852 83ED1081 / ...852=RADIO ID cont. + * 1790 03 F E33C03E9B3E + * 1790 04 F 35647DE0C00 + * 1790 05 F C8A83E351E4 + * 1790 06 F 079F592CF37 + * 1790 07 F 94B30000000 + * + * Example 2 + * 1590 1BBD070100 4 955 + * 1790 01 4 BEE002AEE67 + * 1790 02 4 85283ED1081 + * 1790 03 4 E33C03E9B3E + * 1790 04 4 35647DE0C00 + * 1790 05 4 C8A83E351E4 + * 1790 06 4 079F592CF37 + * 1790 07 4 94B30000000 + * + * The LCO 15 header starts with the Talkgroup value and seems to identify the count of continuation messages to + * follow (0x07). In the LCO 17 continuation messages, the first octet after the vendor seems to be a message record + * count ID, ranging from 1 to 7. The next nibble in the continuation messages seems to be a continuation message + * sequence number to let you know that the continuation messages are all part of the same sequence. + * + * The reprogramming payload sequence starts by sending the full SUID for the radio (BEE00.2AE.E67852). + */ +public class LCMotorolaRadioReprogramHeader extends LinkControlWord +{ + private static final IntField TALKGROUP = IntField.length16(OCTET_2_BIT_16); + private static final IntField RECORD_COUNT = IntField.length8(OCTET_4_BIT_32); + private static final IntField SEQUENCE_NUMBER = IntField.length4(OCTET_7_BIT_56); + private static final IntField SEQUENCE_CHECKSUM = IntField.length12(OCTET_7_BIT_56 + 4); + private Identifier mTalkgroup; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCMotorolaRadioReprogramHeader(CorrectedBinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("**CRC-FAILED** "); + } + + if(isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + sb.append("MOTOROLA RADIO REPROGRAM HEADER"); + sb.append(" TG:").append(getTalkgroup()); + sb.append(" RECORD COUNT:").append(getRecordCount()); + sb.append(" SEQUENCE:").append(getSequenceNumber()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + /** + * Talkgroup + */ + public Identifier getTalkgroup() + { + if(mTalkgroup == null) + { + mTalkgroup = APCO25Talkgroup.create(getInt(TALKGROUP)); + } + + return mTalkgroup; + } + + /** + * Count of continuation messages to follow + * @return + */ + public int getRecordCount() + { + return getInt(RECORD_COUNT); + } + + /** + * Sequence number + */ + public int getSequenceNumber() + { + return getInt(SEQUENCE_NUMBER); + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaRadioReprogramRecord.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaRadioReprogramRecord.java new file mode 100644 index 000000000..f22710bc9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaRadioReprogramRecord.java @@ -0,0 +1,89 @@ +/* + * ***************************************************************************** + * 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.lc.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Link Control opcode 0x17 (23). This is possibly a radio reprogramming record segment that is used + * in combination with LCO 0x15. See notes in header of LCMotorolaRadioReprogramHeader class. + */ +public class LCMotorolaRadioReprogramRecord extends LinkControlWord +{ + private static final IntField RECORD_NUMBER = IntField.length8(OCTET_2_BIT_16); + private static final IntField SEQUENCE_NUMBER = IntField.length4(OCTET_3_BIT_24); + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCMotorolaRadioReprogramRecord(CorrectedBinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(!isValid()) + { + sb.append("**CRC-FAILED** "); + } + + if(isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + + sb.append("MOTOROLA RADIO REPROGRAM RECORD:").append(getRecordNumber()); + sb.append(" OF SEQUENCE:").append(getSequenceNumber()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + /** + * Record number + */ + public int getRecordNumber() + { + return getInt(RECORD_NUMBER); + } + + /** + * Sequence number + */ + public int getSequenceNumber() + { + return getInt(SEQUENCE_NUMBER); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaTalkComplete.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaTalkComplete.java index 002a39dea..249434d48 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaTalkComplete.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaTalkComplete.java @@ -1,32 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.motorola; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; @@ -35,10 +32,9 @@ */ public class LCMotorolaTalkComplete extends LinkControlWord { - private static final int[] UNKNOWN_FIELD_1 = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] UNKNOWN_FIELD_2 = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, - 67, 68, 69, 70, 71}; + private static final IntField UNKNOWN_FIELD_1 = IntField.length8(OCTET_2_BIT_16); + private static final IntField UNKNOWN_FIELD_2 = IntField.length8(OCTET_2_BIT_16); + private static final IntField ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mAddress; private List mIdentifiers; @@ -48,7 +44,7 @@ public class LCMotorolaTalkComplete extends LinkControlWord * * @param message */ - public LCMotorolaTalkComplete(BinaryMessage message) + public LCMotorolaTalkComplete(CorrectedBinaryMessage message) { super(message); } @@ -72,19 +68,18 @@ public String toString() sb.append(" BY:").append(getAddress()); sb.append(" UNK1:").append(getUnknownField1()); sb.append(" UNK2:").append(getUnknownField2()); - sb.append(" MSG:").append(getMessage().toHexString()); } return sb.toString(); } public String getUnknownField1() { - return getMessage().getHex(UNKNOWN_FIELD_1, 2); + return getMessage().getHex(UNKNOWN_FIELD_1); } public String getUnknownField2() { - return getMessage().getHex(UNKNOWN_FIELD_2, 2); + return getMessage().getHex(UNKNOWN_FIELD_2); } /** @@ -94,7 +89,7 @@ public Identifier getAddress() { if(mAddress == null) { - mAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(ADDRESS)); + mAddress = APCO25RadioIdentifier.createFrom(getInt(ADDRESS)); } return mAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaUnitGPS.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaUnitGPS.java index ae48f5923..b0f1f2a8c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaUnitGPS.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaUnitGPS.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -19,30 +19,39 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.FragmentedIntField; import io.github.dsheirer.identifier.Identifier; -import java.util.Collections; +import io.github.dsheirer.module.decode.dmr.identifier.P25Location; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import java.util.ArrayList; import java.util.List; +import org.jdesktop.swingx.mapviewer.GeoPosition; /** * Motorola Unit Self-Reported GPS Location */ -public class LCMotorolaUnitGPS extends MotorolaLinkControlWord +public class LCMotorolaUnitGPS extends LinkControlWord { private static final double LATITUDE_MULTIPLIER = 90.0 / 0x7FFFFF; private static final double LONGITUDE_MULTIPLIER = 180.0 / 0x7FFFFF; - private static final int[] UNKNOWN = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int LATITUDE_SIGN = 24; - private static final int[] LATITUDE = {25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final FragmentedIntField LATITUDE = FragmentedIntField.of(25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47); private static final int LONGITUDE_SIGN = 48; - private static final int[] LONGITUDE = {49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final FragmentedIntField LONGITUDE = FragmentedIntField.of(49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71); + + private P25Location mLocation; + private GeoPosition mGeoPosition; + private List mIdentifiers; /** * Constructs an instance * @param message binary */ - public LCMotorolaUnitGPS(BinaryMessage message) + public LCMotorolaUnitGPS(CorrectedBinaryMessage message) { super(message); } @@ -55,13 +64,41 @@ public String toString() return sb.toString(); } + /** + * GPS Location + * @return location in decimal degrees + */ + public P25Location getLocation() + { + if(mLocation == null) + { + mLocation = P25Location.createFrom(getLatitude(), getLongitude()); + } + + return mLocation; + } + + /** + * Geo position + * @return position + */ + public GeoPosition getGeoPosition() + { + if(mGeoPosition == null) + { + mGeoPosition = new GeoPosition(getLatitude(), getLongitude()); + } + + return mGeoPosition; + } + /** * GPS Latitude value. * @return value in decimal degrees */ public double getLatitude() { - return getMessage().getInt(LATITUDE) * LATITUDE_MULTIPLIER * (getMessage().get(LATITUDE_SIGN) ? -1 : 1); + return getInt(LATITUDE) * LATITUDE_MULTIPLIER * (getMessage().get(LATITUDE_SIGN) ? -1 : 1); } /** @@ -70,7 +107,7 @@ public double getLatitude() */ public double getLongitude() { - return getMessage().getInt(LONGITUDE) * LONGITUDE_MULTIPLIER * (getMessage().get(LONGITUDE_SIGN) ? -1 : 1); + return getInt(LONGITUDE) * LONGITUDE_MULTIPLIER * (getMessage().get(LONGITUDE_SIGN) ? -1 : 1); } /** @@ -79,6 +116,12 @@ public double getLongitude() @Override public List getIdentifiers() { - return Collections.emptyList(); + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLocation()); + } + + return mIdentifiers; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaUnknownOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaUnknownOpcode.java index aa6a5d6f9..82858cd35 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaUnknownOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaUnknownOpcode.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,28 +14,28 @@ * * 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.lc.motorola; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; - +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import java.util.Collections; import java.util.List; /** * Unknown link control word. */ -public class LCMotorolaUnknownOpcode extends MotorolaLinkControlWord +public class LCMotorolaUnknownOpcode extends LinkControlWord { /** * Constructs a Link Control Word from the binary message sequence. * * @param message */ - public LCMotorolaUnknownOpcode(BinaryMessage message) + public LCMotorolaUnknownOpcode(CorrectedBinaryMessage message) { super(message); } @@ -51,7 +50,7 @@ public List getIdentifiers() public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(" MOTOROLA UNKNOWN/UNRECOGNIZED OPCODE:").append(getOpcode()); + sb.append(" MOTOROLA UNKNOWN OPCODE:").append(getOpcodeNumber()); sb.append(" MSG:").append(getMessage().toHexString()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCAdjacentSiteStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCAdjacentSiteStatusBroadcast.java index b856f68bd..402af9477 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCAdjacentSiteStatusBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCAdjacentSiteStatusBroadcast.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; @@ -33,7 +31,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.List; @@ -42,13 +39,13 @@ */ public class LCAdjacentSiteStatusBroadcast extends LinkControlWord implements IFrequencyBandReceiver { - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] RFSS = {32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SITE = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final IntField LRA = IntField.length8(OCTET_1_BIT_8); + private static final IntField SYSTEM = IntField.length12(OCTET_2_BIT_16 + 4); + private static final IntField RFSS = IntField.length8(OCTET_4_BIT_32); + private static final IntField SITE = IntField.length8(OCTET_5_BIT_40); + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_48); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_48 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_8_BIT_64); private List mIdentifiers; private Identifier mLRA; @@ -63,7 +60,7 @@ public class LCAdjacentSiteStatusBroadcast extends LinkControlWord implements IF * * @param message */ - public LCAdjacentSiteStatusBroadcast(BinaryMessage message) + public LCAdjacentSiteStatusBroadcast(CorrectedBinaryMessage message) { super(message); } @@ -85,7 +82,7 @@ public Identifier getLocationRegistrationArea() { if(mLRA == null) { - mLRA = APCO25Lra.create(getMessage().getInt(LRA)); + mLRA = APCO25Lra.create(getInt(LRA)); } return mLRA; @@ -95,7 +92,7 @@ public Identifier getSystem() { if(mSystem == null) { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + mSystem = APCO25System.create(getInt(SYSTEM)); } return mSystem; @@ -105,7 +102,7 @@ public Identifier getRfss() { if(mRFSS == null) { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + mRFSS = APCO25Rfss.create(getInt(RFSS)); } return mRFSS; @@ -115,7 +112,7 @@ public Identifier getSite() { if(mSite == null) { - mSite = APCO25Site.create(getMessage().getInt(SITE)); + mSite = APCO25Site.create(getInt(SITE)); } return mSite; @@ -125,8 +122,7 @@ public IChannelDescriptor getChannel() { if(mChannel == null) { - mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), - getMessage().getInt(CHANNEL_NUMBER)); + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); } return mChannel; @@ -136,7 +132,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = new SystemServiceClass(getMessage().getInt(SERVICE_CLASS)); + mSystemServiceClass = new SystemServiceClass(getInt(SERVICE_CLASS)); } return mSystemServiceClass; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCAdjacentSiteStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCAdjacentSiteStatusBroadcastExplicit.java index 186081e2e..d65d1f4d7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCAdjacentSiteStatusBroadcastExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCAdjacentSiteStatusBroadcastExplicit.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; @@ -32,23 +30,23 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Secondary control channel broadcast explicit information for V/UHF channels + * Adjacent site status broadcast (ie neighbor). */ public class LCAdjacentSiteStatusBroadcastExplicit extends LinkControlWord implements IFrequencyBandReceiver { - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] DOWNLINK_FREQUENCY_BAND = {16, 17, 18, 19}; - private static final int[] DOWNLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] RFSS = {32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SITE = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] UPLINK_FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] UPLINK_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final IntField LRA = IntField.length8(OCTET_1_BIT_8); + private static final IntField DOWNLINK_FREQUENCY_BAND = IntField.length4(OCTET_2_BIT_16); + private static final IntField DOWNLINK_CHANNEL_NUMBER = IntField.length12(OCTET_2_BIT_16 + 4); + private static final IntField RFSS = IntField.length8(OCTET_4_BIT_32); + private static final IntField SITE = IntField.length8(OCTET_5_BIT_40); + private static final IntField UPLINK_FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_48); + private static final IntField UPLINK_CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_48 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_8_BIT_64); private List mIdentifiers; private Identifier mLRA; @@ -62,7 +60,7 @@ public class LCAdjacentSiteStatusBroadcastExplicit extends LinkControlWord imple * * @param message */ - public LCAdjacentSiteStatusBroadcastExplicit(BinaryMessage message) + public LCAdjacentSiteStatusBroadcastExplicit(CorrectedBinaryMessage message) { super(message); } @@ -83,7 +81,7 @@ public Identifier getLocationRegistrationArea() { if(mLRA == null) { - mLRA = APCO25Lra.create(getMessage().getInt(LRA)); + mLRA = APCO25Lra.create(getInt(LRA)); } return mLRA; @@ -93,7 +91,7 @@ public Identifier getRfss() { if(mRFSS == null) { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + mRFSS = APCO25Rfss.create(getInt(RFSS)); } return mRFSS; @@ -103,7 +101,7 @@ public Identifier getSite() { if(mSite == null) { - mSite = APCO25Site.create(getMessage().getInt(SITE)); + mSite = APCO25Site.create(getInt(SITE)); } return mSite; @@ -113,9 +111,8 @@ public IChannelDescriptor getChannel() { if(mChannel == null) { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), - getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), - getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + mChannel = APCO25ExplicitChannel.create(getInt(DOWNLINK_FREQUENCY_BAND), getInt(DOWNLINK_CHANNEL_NUMBER), + getInt(UPLINK_FREQUENCY_BAND), getInt(UPLINK_CHANNEL_NUMBER)); } return mChannel; @@ -125,7 +122,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = new SystemServiceClass(getMessage().getInt(SERVICE_CLASS)); + mSystemServiceClass = new SystemServiceClass(getInt(SERVICE_CLASS)); } return mSystemServiceClass; @@ -152,8 +149,6 @@ public List getIdentifiers() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallAlert.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallAlert.java index 8ea04f47f..596cb36b5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallAlert.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallAlert.java @@ -1,32 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; @@ -35,12 +32,8 @@ */ public class LCCallAlert extends LinkControlWord { - private static final int[] RESERVED = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mTargetAddress; private Identifier mSourceAddress; private List mIdentifiers; @@ -50,7 +43,7 @@ public class LCCallAlert extends LinkControlWord * * @param message */ - public LCCallAlert(BinaryMessage message) + public LCCallAlert(CorrectedBinaryMessage message) { super(message); } @@ -71,7 +64,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -84,7 +77,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS)); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallTermination.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallTermination.java index 1b4a88359..db9829dfc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallTermination.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallTermination.java @@ -1,32 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; @@ -37,10 +34,7 @@ public class LCCallTermination extends LinkControlWord { private static final int MOTOROLA_SYSTEM_CONTROLLER = 0xFFFFFD; private static final int HARRIS_SYSTEM_CONTROLLER = 0; - - private static final int[] ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, - 67, 68, 69, 70, 71}; - + private static final IntField ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mAddress; private List mIdentifiers; @@ -49,7 +43,7 @@ public class LCCallTermination extends LinkControlWord * * @param message */ - public LCCallTermination(BinaryMessage message) + public LCCallTermination(CorrectedBinaryMessage message) { super(message); } @@ -69,7 +63,7 @@ public Identifier getAddress() { if(mAddress == null) { - mAddress = APCO25Talkgroup.create(getMessage().getInt(ADDRESS)); + mAddress = APCO25Talkgroup.create(getInt(ADDRESS)); } return mAddress; @@ -80,7 +74,7 @@ public Identifier getAddress() */ public boolean isNetworkCommandedTeardown() { - int address = getMessage().getInt(ADDRESS); + int address = getInt(ADDRESS); return address == MOTOROLA_SYSTEM_CONTROLLER || address == HARRIS_SYSTEM_CONTROLLER; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCFrequencyBandUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCChannelIdentifierUpdate.java similarity index 52% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCFrequencyBandUpdate.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCChannelIdentifierUpdate.java index 893b83d58..3397cb7fe 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCFrequencyBandUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCChannelIdentifierUpdate.java @@ -1,54 +1,52 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +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.phase1.message.IFrequencyBand; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.Collections; import java.util.List; /** - * Echo of the status update from a subscriber when the destination of the update is another subscriber unit. + * Channel identifier update */ -public class LCFrequencyBandUpdate extends LinkControlWord implements IFrequencyBand +public class LCChannelIdentifierUpdate extends LinkControlWord implements IFrequencyBand { - private static final int[] FREQUENCY_BAND_IDENTIFIER = {8, 9, 10, 11}; - private static final int[] BANDWIDTH = {12, 13, 14, 15, 16, 17, 18, 19, 20}; + private static final IntField FREQUENCY_BAND_IDENTIFIER = IntField.length4(OCTET_1_BIT_8); + private static final FragmentedIntField BANDWIDTH = FragmentedIntField.of(12, 13, 14, 15, 16, 17, 18, 19, 20); private static final int TRANSMIT_OFFSET_SIGN = 21; - private static final int[] TRANSMIT_OFFSET = {22, 23, 24, 25, 26, 27, 28, 29}; - private static final int[] CHANNEL_SPACING = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] BASE_FREQUENCY = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; + private static final FragmentedIntField TRANSMIT_OFFSET = FragmentedIntField.of(22, 23, 24, 25, 26, 27, 28, 29); + private static final FragmentedIntField CHANNEL_SPACING = FragmentedIntField.of(30, 31, 32, 33, 34, 35, 36, 37, 38, 39); + private static final LongField BASE_FREQUENCY = LongField.length32(OCTET_5_BIT_40); /** * Constructs a Link Control Word from the binary message sequence. * * @param message */ - public LCFrequencyBandUpdate(BinaryMessage message) + public LCChannelIdentifierUpdate(CorrectedBinaryMessage message) { super(message); } @@ -68,31 +66,31 @@ public String toString() @Override public int getIdentifier() { - return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER); + return getInt(FREQUENCY_BAND_IDENTIFIER); } @Override public long getChannelSpacing() { - return getMessage().getInt(CHANNEL_SPACING) * 125l; + return getInt(CHANNEL_SPACING) * 125l; } @Override public long getBaseFrequency() { - return getMessage().getLong(BASE_FREQUENCY) * 5l; + return getLong(BASE_FREQUENCY) * 5l; } @Override public int getBandwidth() { - return getMessage().getInt(BANDWIDTH) * 125; + return getInt(BANDWIDTH) * 125; } @Override public long getTransmitOffset() { - long offset = getMessage().getLong(TRANSMIT_OFFSET) * getChannelSpacing(); + long offset = getInt(TRANSMIT_OFFSET) * getChannelSpacing(); if(!getMessage().get(TRANSMIT_OFFSET_SIGN)) { @@ -107,7 +105,7 @@ public long getTransmitOffset() */ public boolean hasTransmitOffset() { - return getMessage().getInt(TRANSMIT_OFFSET) != 0x80; + return getInt(TRANSMIT_OFFSET) != 0x80; } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCFrequencyBandUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCChannelIdentifierUpdateVU.java similarity index 54% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCFrequencyBandUpdateExplicit.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCChannelIdentifierUpdateVU.java index ae97a572a..aea0dc386 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCFrequencyBandUpdateExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCChannelIdentifierUpdateVU.java @@ -1,54 +1,52 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +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.phase1.message.IFrequencyBand; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.Collections; import java.util.List; /** - * Identifier update explicit + * Channel identifier update VHF/UHF */ -public class LCFrequencyBandUpdateExplicit extends LinkControlWord implements IFrequencyBand +public class LCChannelIdentifierUpdateVU extends LinkControlWord implements IFrequencyBand { - private static final int[] FREQUENCY_BAND_IDENTIFIER = {8, 9, 10, 11}; - private static final int[] BANDWIDTH = {12, 13, 14, 15}; + private static final IntField FREQUENCY_BAND_IDENTIFIER = IntField.length4(OCTET_1_BIT_8); + private static final IntField BANDWIDTH = IntField.length4(OCTET_1_BIT_8 + 4); private static final int TRANSMIT_OFFSET_SIGN = 16; - private static final int[] TRANSMIT_OFFSET = {17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}; - private static final int[] CHANNEL_SPACING = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] BASE_FREQUENCY = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final FragmentedIntField TRANSMIT_OFFSET = FragmentedIntField.of(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29); + private static final FragmentedIntField CHANNEL_SPACING = FragmentedIntField.of(30, 31, 32, 33, 34, 35, 36, 37, 38, 39); + private static final LongField BASE_FREQUENCY = LongField.length32(OCTET_5_BIT_40); /** * Constructs a Link Control Word from the binary message sequence. * * @param message */ - public LCFrequencyBandUpdateExplicit(BinaryMessage message) + public LCChannelIdentifierUpdateVU(CorrectedBinaryMessage message) { super(message); } @@ -68,25 +66,25 @@ public String toString() @Override public int getIdentifier() { - return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER); + return getInt(FREQUENCY_BAND_IDENTIFIER); } @Override public long getChannelSpacing() { - return getMessage().getInt(CHANNEL_SPACING) * 125l; + return getInt(CHANNEL_SPACING) * 125l; } @Override public long getBaseFrequency() { - return getMessage().getLong(BASE_FREQUENCY) * 5l; + return getLong(BASE_FREQUENCY) * 5l; } @Override public int getBandwidth() { - int bandwidth = getMessage().getInt(BANDWIDTH); + int bandwidth = getInt(BANDWIDTH); if(bandwidth == 0x4) { @@ -103,7 +101,7 @@ else if(bandwidth == 0x5) @Override public long getTransmitOffset() { - long offset = getMessage().getLong(TRANSMIT_OFFSET) * getChannelSpacing(); + long offset = getInt(TRANSMIT_OFFSET) * getChannelSpacing(); if(!getMessage().get(TRANSMIT_OFFSET_SIGN)) { @@ -118,7 +116,7 @@ public long getTransmitOffset() */ public boolean hasTransmitOffset() { - return getMessage().getInt(TRANSMIT_OFFSET) != 0x80; + return getInt(TRANSMIT_OFFSET) != 0x80; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCConventionalFallback.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCConventionalFallback.java new file mode 100644 index 000000000..289bb7566 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCConventionalFallback.java @@ -0,0 +1,105 @@ +/* + * ***************************************************************************** + * 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.lc.standard; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import java.util.Collections; +import java.util.List; + +/** + * Conventional fallback sent on the outbound channel to indicate that the repeater sending it is providing conventional + * fallback service and to indicate which users should use it. + */ +public class LCConventionalFallback extends LinkControlWord +{ + private static final IntField FALLBACK_CHANNEL_ID_1 = IntField.length8(OCTET_3_BIT_24); + private static final IntField FALLBACK_CHANNEL_ID_2 = IntField.length8(OCTET_4_BIT_32); + private static final IntField FALLBACK_CHANNEL_ID_3 = IntField.length8(OCTET_5_BIT_40); + private static final IntField FALLBACK_CHANNEL_ID_4 = IntField.length8(OCTET_6_BIT_48); + private static final IntField FALLBACK_CHANNEL_ID_5 = IntField.length8(OCTET_7_BIT_56); + private static final IntField FALLBACK_CHANNEL_ID_6 = IntField.length8(OCTET_8_BIT_64); + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCConventionalFallback(CorrectedBinaryMessage message) + { + super(message); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + if(!isValid()) + { + sb.append(" [CRC ERROR]"); + } + sb.append(" CONVENTIONAL FALLBACK CHANNELS 1:").append(getFallbackChannel1()); + sb.append(" 2:").append(getFallbackChannel2()); + sb.append(" 3:").append(getFallbackChannel3()); + sb.append(" 4:").append(getFallbackChannel4()); + sb.append(" 5:").append(getFallbackChannel5()); + sb.append(" 6:").append(getFallbackChannel6()); + + return super.toString(); + } + + public int getFallbackChannel1() + { + return getInt(FALLBACK_CHANNEL_ID_1); + } + + public int getFallbackChannel2() + { + return getInt(FALLBACK_CHANNEL_ID_2); + } + + public int getFallbackChannel3() + { + return getInt(FALLBACK_CHANNEL_ID_3); + } + + public int getFallbackChannel4() + { + return getInt(FALLBACK_CHANNEL_ID_4); + } + + public int getFallbackChannel5() + { + return getInt(FALLBACK_CHANNEL_ID_5); + } + + public int getFallbackChannel6() + { + return getInt(FALLBACK_CHANNEL_ID_6); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommand.java index cfabb6f81..f98476a58 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommand.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommand.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -19,12 +19,12 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; - import java.util.ArrayList; import java.util.List; @@ -33,11 +33,9 @@ */ public class LCExtendedFunctionCommand extends LinkControlWord { - private static final int[] EXTENDED_FUNCTION = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] EXTENDED_FUNCTION_ARGUMENTS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; + private static final IntField EXTENDED_FUNCTION = IntField.length16(OCTET_1_BIT_8); + private static final IntField EXTENDED_FUNCTION_ARGUMENTS = IntField.length24(OCTET_3_BIT_24); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mTargetAddress; private List mIdentifiers; @@ -47,7 +45,7 @@ public class LCExtendedFunctionCommand extends LinkControlWord * * @param message */ - public LCExtendedFunctionCommand(BinaryMessage message) + public LCExtendedFunctionCommand(CorrectedBinaryMessage message) { super(message); } @@ -67,7 +65,7 @@ public String toString() */ public ExtendedFunction getExtendedFunction() { - return ExtendedFunction.fromValue(getMessage().getInt(EXTENDED_FUNCTION)); + return ExtendedFunction.fromValue(getInt(EXTENDED_FUNCTION)); } /** @@ -75,7 +73,7 @@ public ExtendedFunction getExtendedFunction() */ public String getExtendedFunctionArguments() { - return getMessage().getHex(EXTENDED_FUNCTION_ARGUMENTS, 6); + return getMessage().getHex(EXTENDED_FUNCTION_ARGUMENTS); } /** @@ -85,7 +83,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommandExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommandExtended.java new file mode 100644 index 000000000..090808755 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommandExtended.java @@ -0,0 +1,135 @@ +/* + * ***************************************************************************** + * 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.lc.standard; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.ExtendedSourceLinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; +import java.util.ArrayList; +import java.util.List; + +/** + * Extended function command to a target address with an extended SUID + */ +public class LCExtendedFunctionCommandExtended extends ExtendedSourceLinkControlWord +{ + private static final IntField EXTENDED_FUNCTION = IntField.length16(OCTET_1_BIT_8); + private static final IntField EXTENDED_FUNCTION_ARGUMENTS = IntField.length24(OCTET_3_BIT_24); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_6_BIT_48); + + private Identifier mTargetAddress; + private FullyQualifiedRadioIdentifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCExtendedFunctionCommandExtended(CorrectedBinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + if(hasSourceIDExtension()) + { + sb.append(" FM:").append(getSourceAddress()); + } + sb.append(" ").append(getExtendedFunction()); + sb.append(" ARGUMENTS:").append(getExtendedFunctionArguments()); + return sb.toString(); + } + + /** + * Source address. Doesn't use the parent class method since the source address value is not available. + */ + @Override + public FullyQualifiedRadioIdentifier getSourceAddress() + { + if(mSourceAddress == null && hasSourceIDExtension()) + { + int wacn = getSourceIDExtension().getWACN(); + int system = getSourceIDExtension().getSystem(); + int id = getSourceIDExtension().getId(); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceAddress; + } + + + /** + * Indicates the type of extended function + */ + public ExtendedFunction getExtendedFunction() + { + return ExtendedFunction.fromValue(getInt(EXTENDED_FUNCTION)); + } + + /** + * Argument(s). May not be required for the class/function and will be set to null (0) if not required. + */ + public String getExtendedFunctionArguments() + { + return getMessage().getHex(EXTENDED_FUNCTION_ARGUMENTS); + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + if(hasSourceIDExtension()) + { + mIdentifiers.add(getSourceAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupAffiliationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupAffiliationQuery.java index 5e2e8132e..1680fb6fe 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupAffiliationQuery.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupAffiliationQuery.java @@ -1,45 +1,39 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; /** - * Current user of an APCO25 channel on both inbound and outbound channels + * Group affiliation query */ public class LCGroupAffiliationQuery extends LinkControlWord { - private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mTargetAddress; private Identifier mSourceAddress; private List mIdentifiers; @@ -49,7 +43,7 @@ public class LCGroupAffiliationQuery extends LinkControlWord * * @param message */ - public LCGroupAffiliationQuery(BinaryMessage message) + public LCGroupAffiliationQuery(CorrectedBinaryMessage message) { super(message); } @@ -70,7 +64,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -83,7 +77,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS)); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUpdate.java index b24fa1fab..b96798671 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUpdate.java @@ -1,52 +1,49 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; /** - * Update detailing other users/channels that are active on the network. + * Group voice channel update */ public class LCGroupVoiceChannelUpdate extends LinkControlWord implements IFrequencyBandReceiver { - public static final int[] FREQUENCY_BAND_A = {8, 9, 10, 11}; - public static final int[] CHANNEL_A = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - public static final int[] GROUP_ADDRESS_A = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - public static final int[] FREQUENCY_BAND_B = {40, 41, 42, 43}; - public static final int[] CHANNEL_B = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - public static final int[] GROUP_ADDRESS_B = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - - private IChannelDescriptor mChannelA; - private IChannelDescriptor mChannelB; + private static final IntField FREQUENCY_BAND_A = IntField.length4(OCTET_1_BIT_8); + private static final IntField CHANNEL_A = IntField.length12(OCTET_1_BIT_8 + 4); + private static final IntField GROUP_ADDRESS_A = IntField.length16(OCTET_3_BIT_24); + private static final IntField FREQUENCY_BAND_B = IntField.length4(OCTET_5_BIT_40); + private static final IntField CHANNEL_B = IntField.length12(OCTET_5_BIT_40 + 4); + private static final IntField GROUP_ADDRESS_B = IntField.length16(OCTET_7_BIT_56); + + private APCO25Channel mChannelA; + private APCO25Channel mChannelB; private Identifier mTalkgroupA; private Identifier mTalkgroupB; private List mIdentifiers; @@ -54,7 +51,7 @@ public class LCGroupVoiceChannelUpdate extends LinkControlWord implements IFrequ /** * Constructs a Link Control Word from the binary message sequence. */ - public LCGroupVoiceChannelUpdate(BinaryMessage message) + public LCGroupVoiceChannelUpdate(CorrectedBinaryMessage message) { super(message); } @@ -92,21 +89,21 @@ public List getIdentifiers() return mIdentifiers; } - public IChannelDescriptor getChannelA() + public APCO25Channel getChannelA() { if(mChannelA == null) { - mChannelA = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_A), getMessage().getInt(CHANNEL_A)); + mChannelA = APCO25Channel.create(getInt(FREQUENCY_BAND_A), getInt(CHANNEL_A)); } return mChannelA; } - public IChannelDescriptor getChannelB() + public APCO25Channel getChannelB() { if(mChannelB == null) { - mChannelB = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_B), getMessage().getInt(CHANNEL_B)); + mChannelB = APCO25Channel.create(getInt(FREQUENCY_BAND_B), getInt(CHANNEL_B)); } return mChannelB; @@ -114,14 +111,14 @@ public IChannelDescriptor getChannelB() public boolean hasChannelB() { - return getMessage().getInt(CHANNEL_B) != 0 && getMessage().getInt(GROUP_ADDRESS_A) != getMessage().getInt(GROUP_ADDRESS_B); + return getInt(CHANNEL_B) != 0 && getInt(GROUP_ADDRESS_A) != getInt(GROUP_ADDRESS_B); } public Identifier getGroupAddressA() { if(mTalkgroupA == null) { - mTalkgroupA = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_A)); + mTalkgroupA = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_A)); } return mTalkgroupA; @@ -131,7 +128,7 @@ public Identifier getGroupAddressB() { if(mTalkgroupB == null) { - mTalkgroupB = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_B)); + mTalkgroupB = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_B)); } return mTalkgroupB; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUpdateExplicit.java index 85bf1b42c..4819b4274 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUpdateExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUpdateExplicit.java @@ -1,61 +1,55 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; +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.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - +import io.github.dsheirer.module.decode.p25.phase1.message.lc.VoiceLinkControlMessage; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Update detailing other users/channels that are active on the network. + * Group voice channel update explicit */ -public class LCGroupVoiceChannelUpdateExplicit extends LinkControlWord implements IFrequencyBandReceiver +public class LCGroupVoiceChannelUpdateExplicit extends VoiceLinkControlMessage implements IFrequencyBandReceiver { - public static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; - public static final int[] GROUP_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - public static final int[] DOWNLINK_FREQUENCY_BAND = {40, 41, 42, 43}; - public static final int[] DOWNLINK_CHANNEL = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - public static final int[] UPLINK_FREQUENCY_BAND = {56, 57, 58, 59}; - public static final int[] UPLINK_CHANNEL = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - - private VoiceServiceOptions mVoiceServiceOptions; - private IChannelDescriptor mChannel; - private List mChannels; + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_3_BIT_24); + private static final IntField DOWNLINK_FREQUENCY_BAND = IntField.length4(OCTET_5_BIT_40); + private static final IntField DOWNLINK_CHANNEL = IntField.length12(OCTET_5_BIT_40 + 4); + private static final IntField UPLINK_FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_56); + private static final IntField UPLINK_CHANNEL = IntField.length12(OCTET_7_BIT_56 + 4); + private APCO25Channel mChannel; private Identifier mTalkgroup; private List mIdentifiers; /** * Constructs a Link Control Word from the binary message sequence. */ - public LCGroupVoiceChannelUpdateExplicit(BinaryMessage message) + public LCGroupVoiceChannelUpdateExplicit(CorrectedBinaryMessage message) { super(message); } @@ -66,24 +60,11 @@ public String toString() sb.append(getMessageStub()); sb.append(" TALKGROUP:").append(getGroupAddress()); sb.append(" ").append(getChannel()); - sb.append(" ").append(getVoiceServiceOptions()); + sb.append(" ").append(getServiceOptions()); return sb.toString(); } - /** - * Service options for this channel - */ - public VoiceServiceOptions getVoiceServiceOptions() - { - if(mVoiceServiceOptions == null) - { - mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); - } - - return mVoiceServiceOptions; - } - @Override public List getIdentifiers() { @@ -96,13 +77,12 @@ public List getIdentifiers() return mIdentifiers; } - public IChannelDescriptor getChannel() + public APCO25Channel getChannel() { if(mChannel == null) { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), - getMessage().getInt(DOWNLINK_CHANNEL), getMessage().getInt(UPLINK_FREQUENCY_BAND), - getMessage().getInt(UPLINK_CHANNEL)); + mChannel = APCO25ExplicitChannel.create(getInt(DOWNLINK_FREQUENCY_BAND), getInt(DOWNLINK_CHANNEL), + getInt(UPLINK_FREQUENCY_BAND), getInt(UPLINK_CHANNEL)); } return mChannel; @@ -112,7 +92,7 @@ public Identifier getGroupAddress() { if(mTalkgroup == null) { - mTalkgroup = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS)); + mTalkgroup = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); } return mTalkgroup; @@ -121,8 +101,6 @@ public Identifier getGroupAddress() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUser.java index 055cd29f1..b1d107be6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUser.java @@ -1,48 +1,40 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - +import io.github.dsheirer.module.decode.p25.phase1.message.lc.VoiceLinkControlMessage; import java.util.ArrayList; import java.util.List; /** - * Current user of an APCO25 channel on both inbound and outbound channels + * Group voice channel user. */ -public class LCGroupVoiceChannelUser extends LinkControlWord +public class LCGroupVoiceChannelUser extends VoiceLinkControlMessage { - private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] GROUP_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - - private VoiceServiceOptions mVoiceServiceOptions; + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_4_BIT_32); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mGroupAddress; private Identifier mSourceAddress; private List mIdentifiers; @@ -52,7 +44,7 @@ public class LCGroupVoiceChannelUser extends LinkControlWord * * @param message */ - public LCGroupVoiceChannelUser(BinaryMessage message) + public LCGroupVoiceChannelUser(CorrectedBinaryMessage message) { super(message); } @@ -63,23 +55,10 @@ public String toString() sb.append(getMessageStub()); sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getGroupAddress()); - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - /** - * Service Options for this channel - */ - public VoiceServiceOptions getVoiceServiceOptions() - { - if(mVoiceServiceOptions == null) - { - mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); - } - - return mVoiceServiceOptions; - } - /** * Talkgroup address */ @@ -87,7 +66,7 @@ public Identifier getGroupAddress() { if(mGroupAddress == null) { - mGroupAddress = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS)); + mGroupAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); } return mGroupAddress; @@ -100,12 +79,20 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS)); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; } + /** + * Indicates if the source address is valid and non-zero + */ + public boolean hasSourceAddress() + { + return getInt(SOURCE_ADDRESS) > 0; + } + /** * List of identifiers contained in this message */ @@ -116,7 +103,10 @@ public List getIdentifiers() { mIdentifiers = new ArrayList<>(); mIdentifiers.add(getGroupAddress()); - mIdentifiers.add(getSourceAddress()); + if(hasSourceAddress()) + { + mIdentifiers.add(getSourceAddress()); + } } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdate.java index c72eba775..b0209b750 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdate.java @@ -1,46 +1,41 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; /** - * Echo of the status update from a subscriber when the destination of the update is another subscriber unit. + * Echo of the short data message from a subscriber when the destination of the update is another subscriber unit. */ public class LCMessageUpdate extends LinkControlWord { - private static final int[] MESSAGE = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; + private static final IntField MESSAGE = IntField.length16(OCTET_1_BIT_8); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mShortDataMessage; private Identifier mTargetAddress; @@ -52,7 +47,7 @@ public class LCMessageUpdate extends LinkControlWord * * @param message */ - public LCMessageUpdate(BinaryMessage message) + public LCMessageUpdate(CorrectedBinaryMessage message) { super(message); } @@ -71,7 +66,7 @@ public Identifier getShortDataMessage() { if(mShortDataMessage == null) { - mShortDataMessage = APCO25ShortDataMessage.create(getMessage().getInt(MESSAGE)); + mShortDataMessage = APCO25ShortDataMessage.create(getInt(MESSAGE)); } return mShortDataMessage; @@ -84,7 +79,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -97,7 +92,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS)); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdateExtended.java new file mode 100644 index 000000000..08499259a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdateExtended.java @@ -0,0 +1,108 @@ +/* + * ***************************************************************************** + * 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.lc.standard; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.ExtendedSourceLinkControlWord; +import java.util.ArrayList; +import java.util.List; + +/** + * Message Update with extended SUID + */ +public class LCMessageUpdateExtended extends ExtendedSourceLinkControlWord +{ + private static final IntField MESSAGE = IntField.length16(OCTET_1_BIT_8); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + + private Identifier mShortDataMessage; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCMessageUpdateExtended(CorrectedBinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + if(hasSourceIDExtension()) + { + sb.append(" FM:").append(getSourceAddress()); + } + sb.append(" SDM:").append(getShortDataMessage()); + return sb.toString(); + } + + public Identifier getShortDataMessage() + { + if(mShortDataMessage == null) + { + mShortDataMessage = APCO25ShortDataMessage.create(getInt(MESSAGE)); + } + + return mShortDataMessage; + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + if(hasSourceIDExtension()) + { + mIdentifiers.add(getSourceAddress()); + } + mIdentifiers.add(getShortDataMessage()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCNetworkStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCNetworkStatusBroadcast.java index e11051d4a..41d779de1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCNetworkStatusBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCNetworkStatusBroadcast.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25System; @@ -31,7 +29,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.List; @@ -40,12 +37,11 @@ */ public class LCNetworkStatusBroadcast extends LinkControlWord implements IFrequencyBandReceiver { - private static final int[] RESERVED = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] WACN = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; - private static final int[] SYSTEM = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final IntField WACN = IntField.length20(OCTET_2_BIT_16); + private static final IntField SYSTEM = IntField.length12(OCTET_4_BIT_32 + 4); + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_48); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_48 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_8_BIT_64); private List mIdentifiers; private Identifier mWACN; @@ -58,7 +54,7 @@ public class LCNetworkStatusBroadcast extends LinkControlWord implements IFreque * * @param message */ - public LCNetworkStatusBroadcast(BinaryMessage message) + public LCNetworkStatusBroadcast(CorrectedBinaryMessage message) { super(message); } @@ -78,7 +74,7 @@ public Identifier getWACN() { if(mWACN == null) { - mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + mWACN = APCO25Wacn.create(getInt(WACN)); } return mWACN; @@ -88,7 +84,7 @@ public Identifier getSystem() { if(mSystem == null) { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + mSystem = APCO25System.create(getInt(SYSTEM)); } return mSystem; @@ -98,8 +94,7 @@ public IChannelDescriptor getChannel() { if(mChannel == null) { - mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), - getMessage().getInt(CHANNEL_NUMBER)); + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); } return mChannel; @@ -109,7 +104,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = new SystemServiceClass(getMessage().getInt(SERVICE_CLASS)); + mSystemServiceClass = new SystemServiceClass(getInt(SERVICE_CLASS)); } return mSystemServiceClass; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCNetworkStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCNetworkStatusBroadcastExplicit.java index d94992f0c..b8873c565 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCNetworkStatusBroadcastExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCNetworkStatusBroadcastExplicit.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,12 +14,13 @@ * * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25System; @@ -28,22 +28,21 @@ import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Secondary control channel broadcast information. + * Network status broadcast explicit */ public class LCNetworkStatusBroadcastExplicit extends LinkControlWord implements IFrequencyBandReceiver { - private static final int[] WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; - private static final int[] SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] DOWNLINK_FREQUENCY_BAND = {40, 41, 42, 43}; - private static final int[] DOWNLINK_CHANNEL_NUMBER = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] UPLINK_FREQUENCY_BAND = {56, 57, 58, 59}; - private static final int[] UPLINK_CHANNEL_NUMBER = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField WACN = IntField.length20(OCTET_1_BIT_8); + private static final IntField SYSTEM = IntField.length12(OCTET_3_BIT_24 + 4); + private static final IntField DOWNLINK_FREQUENCY_BAND = IntField.length4(OCTET_5_BIT_40); + private static final IntField DOWNLINK_CHANNEL_NUMBER = IntField.length12(OCTET_5_BIT_40 + 4); + private static final IntField UPLINK_FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_56); + private static final IntField UPLINK_CHANNEL_NUMBER = IntField.length12(OCTET_7_BIT_56 + 4); private List mIdentifiers; private Identifier mWACN; private Identifier mSystem; @@ -54,7 +53,7 @@ public class LCNetworkStatusBroadcastExplicit extends LinkControlWord implements * * @param message */ - public LCNetworkStatusBroadcastExplicit(BinaryMessage message) + public LCNetworkStatusBroadcastExplicit(CorrectedBinaryMessage message) { super(message); } @@ -73,7 +72,7 @@ public Identifier getWACN() { if(mWACN == null) { - mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); + mWACN = APCO25Wacn.create(getInt(WACN)); } return mWACN; @@ -83,7 +82,7 @@ public Identifier getSystem() { if(mSystem == null) { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + mSystem = APCO25System.create(getInt(SYSTEM)); } return mSystem; @@ -93,9 +92,8 @@ public IChannelDescriptor getChannel() { if(mChannel == null) { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), - getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), - getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + mChannel = APCO25ExplicitChannel.create(getInt(DOWNLINK_FREQUENCY_BAND), getInt(DOWNLINK_CHANNEL_NUMBER), + getInt(UPLINK_FREQUENCY_BAND), getInt(UPLINK_CHANNEL_NUMBER)); } return mChannel; @@ -120,8 +118,6 @@ public List getIdentifiers() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCProtectionParameterBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCProtectionParameterBroadcast.java index 70b7e3b08..58c4b6cf9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCProtectionParameterBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCProtectionParameterBroadcast.java @@ -1,48 +1,44 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier; import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; /** - * Echo of the status update from a subscriber when the destination of the update is another subscriber unit. + * Protection (ie encryption) parameter broadcast. + * + * Note: this is an obsolete message. */ public class LCProtectionParameterBroadcast extends LinkControlWord { - private static final int[] RESERVED = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] ALGORITHM_ID = {24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] KEY_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField ALGORITHM_ID = IntField.length8(OCTET_3_BIT_24); + private static final IntField KEY_ID = IntField.length16(OCTET_4_BIT_32); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mEncryptionKey; private Identifier mTargetAddress; private List mIdentifiers; @@ -52,7 +48,7 @@ public class LCProtectionParameterBroadcast extends LinkControlWord * * @param message */ - public LCProtectionParameterBroadcast(BinaryMessage message) + public LCProtectionParameterBroadcast(CorrectedBinaryMessage message) { super(message); } @@ -70,8 +66,8 @@ public Identifier getEncryptionKey() { if(mEncryptionKey == null) { - mEncryptionKey = EncryptionKeyIdentifier.create(APCO25EncryptionKey.create(getMessage().getInt(ALGORITHM_ID), - getMessage().getInt(KEY_ID))); + mEncryptionKey = EncryptionKeyIdentifier.create(APCO25EncryptionKey.create(getInt(ALGORITHM_ID), + getInt(KEY_ID))); } return mEncryptionKey; @@ -84,7 +80,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCRFSSStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCRFSSStatusBroadcast.java index 446200807..ec219ba00 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCRFSSStatusBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCRFSSStatusBroadcast.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; @@ -33,7 +31,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.List; @@ -42,13 +39,13 @@ */ public class LCRFSSStatusBroadcast extends LinkControlWord implements IFrequencyBandReceiver { - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] RFSS = {32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SITE = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final IntField LRA = IntField.length8(OCTET_1_BIT_8); + private static final IntField SYSTEM = IntField.length12(OCTET_2_BIT_16 + 4); + private static final IntField RFSS = IntField.length8(OCTET_4_BIT_32); + private static final IntField SITE = IntField.length8(OCTET_5_BIT_40); + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_48); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_48 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_8_BIT_64); private List mIdentifiers; private Identifier mLRA; @@ -63,7 +60,7 @@ public class LCRFSSStatusBroadcast extends LinkControlWord implements IFrequency * * @param message */ - public LCRFSSStatusBroadcast(BinaryMessage message) + public LCRFSSStatusBroadcast(CorrectedBinaryMessage message) { super(message); } @@ -84,7 +81,7 @@ public Identifier getLocationRegistrationArea() { if(mLRA == null) { - mLRA = APCO25Lra.create(getMessage().getInt(LRA)); + mLRA = APCO25Lra.create(getInt(LRA)); } return mLRA; @@ -94,7 +91,7 @@ public Identifier getSystem() { if(mSystem == null) { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); + mSystem = APCO25System.create(getInt(SYSTEM)); } return mSystem; @@ -104,7 +101,7 @@ public Identifier getRfss() { if(mRFSS == null) { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + mRFSS = APCO25Rfss.create(getInt(RFSS)); } return mRFSS; @@ -114,7 +111,7 @@ public Identifier getSite() { if(mSite == null) { - mSite = APCO25Site.create(getMessage().getInt(SITE)); + mSite = APCO25Site.create(getInt(SITE)); } return mSite; @@ -124,8 +121,7 @@ public IChannelDescriptor getChannel() { if(mChannel == null) { - mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND), - getMessage().getInt(CHANNEL_NUMBER)); + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); } return mChannel; @@ -135,7 +131,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = new SystemServiceClass(getMessage().getInt(SERVICE_CLASS)); + mSystemServiceClass = new SystemServiceClass(getInt(SERVICE_CLASS)); } return mSystemServiceClass; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCRFSSStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCRFSSStatusBroadcastExplicit.java index 8e37b0b2b..0d5bcdb0a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCRFSSStatusBroadcastExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCRFSSStatusBroadcastExplicit.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; @@ -32,24 +30,23 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Secondary control channel broadcast information. + * RFSS Status Broadcast Explicit */ public class LCRFSSStatusBroadcastExplicit extends LinkControlWord implements IFrequencyBandReceiver { - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] UPLINK_FREQUENCY_BAND = {16, 17, 18, 19}; - private static final int[] UPLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] RFSS = {32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SITE = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] DOWNLINK_FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] DOWNLINK_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField LRA = IntField.length8(OCTET_1_BIT_8); + private static final IntField UPLINK_FREQUENCY_BAND = IntField.length4(OCTET_2_BIT_16); + private static final IntField UPLINK_CHANNEL_NUMBER = IntField.length12(OCTET_2_BIT_16 + 4); + private static final IntField RFSS = IntField.length8(OCTET_4_BIT_32); + private static final IntField SITE = IntField.length8(OCTET_5_BIT_40); + private static final IntField DOWNLINK_FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_48); + private static final IntField DOWNLINK_CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_48 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_8_BIT_64); private List mIdentifiers; private Identifier mLRA; private Identifier mRFSS; @@ -62,7 +59,7 @@ public class LCRFSSStatusBroadcastExplicit extends LinkControlWord implements IF * * @param message */ - public LCRFSSStatusBroadcastExplicit(BinaryMessage message) + public LCRFSSStatusBroadcastExplicit(CorrectedBinaryMessage message) { super(message); } @@ -82,7 +79,7 @@ public Identifier getLocationRegistrationArea() { if(mLRA == null) { - mLRA = APCO25Lra.create(getMessage().getInt(LRA)); + mLRA = APCO25Lra.create(getInt(LRA)); } return mLRA; @@ -92,7 +89,7 @@ public Identifier getRfss() { if(mRFSS == null) { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + mRFSS = APCO25Rfss.create(getInt(RFSS)); } return mRFSS; @@ -102,7 +99,7 @@ public Identifier getSite() { if(mSite == null) { - mSite = APCO25Site.create(getMessage().getInt(SITE)); + mSite = APCO25Site.create(getInt(SITE)); } return mSite; @@ -112,9 +109,8 @@ public IChannelDescriptor getChannel() { if(mChannel == null) { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), - getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), - getMessage().getInt(UPLINK_CHANNEL_NUMBER)); + mChannel = APCO25ExplicitChannel.create(getInt(DOWNLINK_FREQUENCY_BAND), getInt(DOWNLINK_CHANNEL_NUMBER), + getInt(UPLINK_FREQUENCY_BAND), getInt(UPLINK_CHANNEL_NUMBER)); } return mChannel; @@ -124,7 +120,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = new SystemServiceClass(getMessage().getInt(SERVICE_CLASS)); + mSystemServiceClass = new SystemServiceClass(getInt(SERVICE_CLASS)); } return mSystemServiceClass; @@ -150,8 +146,6 @@ public List getIdentifiers() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSecondaryControlChannelBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSecondaryControlChannelBroadcast.java index 76240daa7..4b1a2b027 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSecondaryControlChannelBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSecondaryControlChannelBroadcast.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; @@ -31,23 +29,22 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.List; /** - * Secondary control channel broadcast information. + * Secondary control channel broadcast. */ public class LCSecondaryControlChannelBroadcast extends LinkControlWord implements IFrequencyBandReceiver { - private static final int[] RFSS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] SITE = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] FREQUENCY_BAND_A = {24, 25, 26, 27}; - private static final int[] CHANNEL_NUMBER_A = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SERVICE_CLASS_A = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] FREQUENCY_BAND_B = {48, 49, 50, 51}; - private static final int[] CHANNEL_NUMBER_B = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SERVICE_CLASS_B = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final IntField RFSS = IntField.length8(OCTET_1_BIT_8); + private static final IntField SITE = IntField.length8(OCTET_2_BIT_16); + private static final IntField FREQUENCY_BAND_A = IntField.length4(OCTET_3_BIT_24); + private static final IntField CHANNEL_NUMBER_A = IntField.length12(OCTET_3_BIT_24 + 4); + private static final IntField SERVICE_CLASS_A = IntField.length8(OCTET_5_BIT_40); + private static final IntField FREQUENCY_BAND_B = IntField.length4(OCTET_6_BIT_48); + private static final IntField CHANNEL_NUMBER_B = IntField.length12(OCTET_6_BIT_48 + 4); + private static final IntField SERVICE_CLASS_B = IntField.length8(OCTET_8_BIT_64); private List mIdentifiers; private Identifier mRFSS; @@ -62,7 +59,7 @@ public class LCSecondaryControlChannelBroadcast extends LinkControlWord implemen * * @param message */ - public LCSecondaryControlChannelBroadcast(BinaryMessage message) + public LCSecondaryControlChannelBroadcast(CorrectedBinaryMessage message) { super(message); } @@ -87,7 +84,7 @@ public Identifier getRFSS() { if(mRFSS == null) { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + mRFSS = APCO25Rfss.create(getInt(RFSS)); } return mRFSS; @@ -97,7 +94,7 @@ public Identifier getSite() { if(mSite == null) { - mSite = APCO25Site.create(getMessage().getInt(SITE)); + mSite = APCO25Site.create(getInt(SITE)); } return mSite; @@ -107,8 +104,7 @@ public IChannelDescriptor getChannelA() { if(mChannelA == null) { - mChannelA = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_A), - getMessage().getInt(CHANNEL_NUMBER_A)); + mChannelA = APCO25Channel.create(getInt(FREQUENCY_BAND_A), getInt(CHANNEL_NUMBER_A)); } return mChannelA; @@ -116,16 +112,14 @@ public IChannelDescriptor getChannelA() private boolean hasChannelB() { - return getMessage().getInt(CHANNEL_NUMBER_A) != getMessage().getInt(CHANNEL_NUMBER_B) && - getMessage().getInt(SERVICE_CLASS_B) != 0; + return getInt(CHANNEL_NUMBER_A) != getInt(CHANNEL_NUMBER_B) && getInt(SERVICE_CLASS_B) != 0; } public IChannelDescriptor getChannelB() { if(mChannelB == null) { - mChannelB = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_B), - getMessage().getInt(CHANNEL_NUMBER_B)); + mChannelB = APCO25Channel.create(getInt(FREQUENCY_BAND_B), getInt(CHANNEL_NUMBER_B)); } return mChannelB; @@ -135,7 +129,7 @@ public SystemServiceClass getSystemServiceClassA() { if(mSystemServiceClassA == null) { - mSystemServiceClassA = new SystemServiceClass(getMessage().getInt(SERVICE_CLASS_A)); + mSystemServiceClassA = new SystemServiceClass(getInt(SERVICE_CLASS_A)); } return mSystemServiceClassA; @@ -146,7 +140,7 @@ public SystemServiceClass getSystemServiceClassB() { if(mSystemServiceClassB == null) { - mSystemServiceClassB = new SystemServiceClass(getMessage().getInt(SERVICE_CLASS_B)); + mSystemServiceClassB = new SystemServiceClass(getInt(SERVICE_CLASS_B)); } return mSystemServiceClassB; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSecondaryControlChannelBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSecondaryControlChannelBroadcastExplicit.java index d8536f043..8c82e7696 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSecondaryControlChannelBroadcastExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSecondaryControlChannelBroadcastExplicit.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; @@ -31,8 +29,8 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -40,15 +38,13 @@ */ public class LCSecondaryControlChannelBroadcastExplicit extends LinkControlWord implements IFrequencyBandReceiver { - private static final int[] RFSS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] SITE = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] DOWNLINK_FREQUENCY_BAND = {24, 25, 26, 27}; - private static final int[] DOWNLINK_CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] UPLINK_FREQUENCY_BAND = {40, 41, 42, 43}; - private static final int[] UPNLINK_CHANNEL_NUMBER = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] SERVICE_CLASS = {56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] RESERVED = {64, 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField RFSS = IntField.length8(OCTET_1_BIT_8); + private static final IntField SITE = IntField.length8(OCTET_2_BIT_16); + private static final IntField DOWNLINK_FREQUENCY_BAND = IntField.length4(OCTET_3_BIT_24); + private static final IntField DOWNLINK_CHANNEL_NUMBER = IntField.length12(OCTET_3_BIT_24 + 4); + private static final IntField UPLINK_FREQUENCY_BAND = IntField.length4(OCTET_5_BIT_40); + private static final IntField UPNLINK_CHANNEL_NUMBER = IntField.length12(OCTET_5_BIT_40 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_7_BIT_56); private List mIdentifiers; private Identifier mRFSS; private Identifier mSite; @@ -60,7 +56,7 @@ public class LCSecondaryControlChannelBroadcastExplicit extends LinkControlWord * * @param message */ - public LCSecondaryControlChannelBroadcastExplicit(BinaryMessage message) + public LCSecondaryControlChannelBroadcastExplicit(CorrectedBinaryMessage message) { super(message); } @@ -79,7 +75,7 @@ public Identifier getRFSS() { if(mRFSS == null) { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS)); + mRFSS = APCO25Rfss.create(getInt(RFSS)); } return mRFSS; @@ -89,7 +85,7 @@ public Identifier getSite() { if(mSite == null) { - mSite = APCO25Site.create(getMessage().getInt(SITE)); + mSite = APCO25Site.create(getInt(SITE)); } return mSite; @@ -99,9 +95,8 @@ public IChannelDescriptor getChannel() { if(mChannel == null) { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(DOWNLINK_FREQUENCY_BAND), - getMessage().getInt(DOWNLINK_CHANNEL_NUMBER), getMessage().getInt(UPLINK_FREQUENCY_BAND), - getMessage().getInt(UPNLINK_CHANNEL_NUMBER)); + mChannel = APCO25ExplicitChannel.create(getInt(DOWNLINK_FREQUENCY_BAND), getInt(DOWNLINK_CHANNEL_NUMBER), + getInt(UPLINK_FREQUENCY_BAND), getInt(UPNLINK_CHANNEL_NUMBER)); } return mChannel; @@ -111,7 +106,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = new SystemServiceClass(getMessage().getInt(SERVICE_CLASS)); + mSystemServiceClass = new SystemServiceClass(getInt(SERVICE_CLASS)); } return mSystemServiceClass; @@ -136,8 +131,6 @@ public List getIdentifiers() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSourceIDExtension.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSourceIDExtension.java new file mode 100644 index 000000000..3784828a5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSourceIDExtension.java @@ -0,0 +1,89 @@ +/* + * ***************************************************************************** + * 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.lc.standard; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import java.util.Collections; +import java.util.List; + +/** + * Source ID extension word. This is used in conjunction with another link control message to carry a fully qualified + * source SUID. + */ +public class LCSourceIDExtension extends LinkControlWord +{ + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_2_BIT_16); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_4_BIT_32 + 4); + private static final IntField SOURCE_SUID_RADIO = IntField.length24(OCTET_6_BIT_48); + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCSourceIDExtension(CorrectedBinaryMessage message) + { + super(message); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" ID:").append(getId()); + return sb.toString(); + } + + /** + * Source SUID WACN value. + */ + public int getWACN() + { + return getInt(SOURCE_SUID_WACN); + } + + /** + * Source SUID System value. + */ + public int getSystem() + { + return getInt(SOURCE_SUID_SYSTEM); + } + + /** + * Source SUID radio value. + */ + public int getId() + { + return getInt(SOURCE_SUID_RADIO); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusQuery.java index ce5ab7dad..fe6797ae7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusQuery.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusQuery.java @@ -1,45 +1,39 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; /** - * Current user of an APCO25 channel on both inbound and outbound channels + * Status query */ public class LCStatusQuery extends LinkControlWord { - private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mTargetAddress; private Identifier mSourceAddress; private List mIdentifiers; @@ -49,7 +43,7 @@ public class LCStatusQuery extends LinkControlWord * * @param message */ - public LCStatusQuery(BinaryMessage message) + public LCStatusQuery(CorrectedBinaryMessage message) { super(message); } @@ -70,7 +64,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -83,7 +77,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS)); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdate.java index f15743a07..94b350761 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdate.java @@ -1,34 +1,31 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UnitStatus; import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UserStatus; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; @@ -37,12 +34,10 @@ */ public class LCStatusUpdate extends LinkControlWord { - private static final int[] UNIT_STATUS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] USER_STATUS = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; + private static final IntField UNIT_STATUS = IntField.length8(OCTET_1_BIT_8); + private static final IntField USER_STATUS = IntField.length8(OCTET_2_BIT_16); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mUnitStatus; private Identifier mUserStatus; @@ -55,7 +50,7 @@ public class LCStatusUpdate extends LinkControlWord * * @param message */ - public LCStatusUpdate(BinaryMessage message) + public LCStatusUpdate(CorrectedBinaryMessage message) { super(message); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdateExtended.java new file mode 100644 index 000000000..7445a7a01 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdateExtended.java @@ -0,0 +1,123 @@ +/* + * ***************************************************************************** + * 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.lc.standard; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UnitStatus; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UserStatus; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.ExtendedSourceLinkControlWord; +import java.util.ArrayList; +import java.util.List; + +/** + * Status Update with extended SUID + */ +public class LCStatusUpdateExtended extends ExtendedSourceLinkControlWord +{ + private static final IntField UNIT_STATUS = IntField.length8(OCTET_1_BIT_8); + private static final IntField USER_STATUS = IntField.length8(OCTET_2_BIT_16); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + + private Identifier mUnitStatus; + private Identifier mUserStatus; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCStatusUpdateExtended(CorrectedBinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + if(hasSourceIDExtension()) + { + sb.append(" FM:").append(getSourceAddress()); + } + sb.append(" UNIT:").append(getUnitStatus()); + sb.append(" USER:").append(getUserStatus()); + return sb.toString(); + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null) + { + mUnitStatus = APCO25UnitStatus.create(getMessage().getInt(UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null) + { + mUserStatus = APCO25UserStatus.create(getMessage().getInt(USER_STATUS)); + } + + return mUserStatus; + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + if(hasSourceIDExtension()) + { + mIdentifiers.add(getSourceAddress()); + } + mIdentifiers.add(getUnitStatus()); + mIdentifiers.add(getUserStatus()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSystemServiceBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSystemServiceBroadcast.java index f095cfaed..16a3976dd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSystemServiceBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSystemServiceBroadcast.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,16 +14,16 @@ * * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.Service; - import java.util.Collections; import java.util.List; @@ -33,18 +32,16 @@ */ public class LCSystemServiceBroadcast extends LinkControlWord { - private static final int[] REQUEST_PRIORITY_LEVEL = {20, 21, 22, 23}; - private static final int[] AVAILABLE_SERVICES = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47}; - private static final int[] SUPPORTED_SERVICES = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, - 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final IntField REQUEST_PRIORITY_LEVEL = IntField.length4(OCTET_2_BIT_16 + 4); + private static final IntField AVAILABLE_SERVICES = IntField.length24(OCTET_3_BIT_24); + private static final IntField SUPPORTED_SERVICES = IntField.length24(OCTET_6_BIT_48); /** * Constructs a Link Control Word from the binary message sequence. * * @param message */ - public LCSystemServiceBroadcast(BinaryMessage message) + public LCSystemServiceBroadcast(CorrectedBinaryMessage message) { super(message); } @@ -64,20 +61,18 @@ public String toString() */ public int getRequestPriorityLevel() { - return getMessage().getInt(REQUEST_PRIORITY_LEVEL); + return getInt(REQUEST_PRIORITY_LEVEL); } public List getAvailableServices() { - long bitmap = getMessage().getLong(AVAILABLE_SERVICES); - return Service.getServices(bitmap); + return Service.getServices(getInt(AVAILABLE_SERVICES)); } public List getSupportedServices() { - long bitmap = getMessage().getLong(SUPPORTED_SERVICES); - return Service.getServices(bitmap); + return Service.getServices(getInt(SUPPORTED_SERVICES)); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCTelephoneInterconnectAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCTelephoneInterconnectAnswerRequest.java index e85ee22d2..57ff7f7d4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCTelephoneInterconnectAnswerRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCTelephoneInterconnectAnswerRequest.java @@ -1,34 +1,31 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.telephone.APCO25TelephoneNumber; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.reference.Digit; - import java.util.ArrayList; import java.util.List; @@ -37,18 +34,17 @@ */ public class LCTelephoneInterconnectAnswerRequest extends LinkControlWord { - private static final int[] DIGIT_1 = {8, 9, 10, 11}; - private static final int[] DIGIT_2 = {12, 13, 14, 15}; - private static final int[] DIGIT_3 = {16, 17, 18, 19}; - private static final int[] DIGIT_4 = {20, 21, 22, 23}; - private static final int[] DIGIT_5 = {24, 25, 26, 27}; - private static final int[] DIGIT_6 = {28, 29, 30, 31}; - private static final int[] DIGIT_7 = {32, 33, 34, 35}; - private static final int[] DIGIT_8 = {36, 37, 38, 39}; - private static final int[] DIGIT_9 = {40, 41, 42, 43}; - private static final int[] DIGIT_10 = {44, 45, 46, 47}; - private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; + private static final IntField DIGIT_1 = IntField.length4(OCTET_1_BIT_8); + private static final IntField DIGIT_2 = IntField.length4(OCTET_1_BIT_8 + 4); + private static final IntField DIGIT_3 = IntField.length4(OCTET_2_BIT_16); + private static final IntField DIGIT_4 = IntField.length4(OCTET_2_BIT_16 + 4); + private static final IntField DIGIT_5 = IntField.length4(OCTET_3_BIT_24); + private static final IntField DIGIT_6 = IntField.length4(OCTET_3_BIT_24 + 4); + private static final IntField DIGIT_7 = IntField.length4(OCTET_4_BIT_32); + private static final IntField DIGIT_8 = IntField.length4(OCTET_4_BIT_32 + 4); + private static final IntField DIGIT_9 = IntField.length4(OCTET_5_BIT_40); + private static final IntField DIGIT_10 = IntField.length4(OCTET_5_BIT_40 + 4); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mTargetAddress; private Identifier mTelephoneNumber; @@ -59,7 +55,7 @@ public class LCTelephoneInterconnectAnswerRequest extends LinkControlWord * * @param message */ - public LCTelephoneInterconnectAnswerRequest(BinaryMessage message) + public LCTelephoneInterconnectAnswerRequest(CorrectedBinaryMessage message) { super(message); } @@ -79,16 +75,16 @@ public Identifier getTelephoneNumber() if(mTelephoneNumber == null) { List digits = new ArrayList<>(); - digits.add(getMessage().getInt(DIGIT_1)); - digits.add(getMessage().getInt(DIGIT_2)); - digits.add(getMessage().getInt(DIGIT_3)); - digits.add(getMessage().getInt(DIGIT_4)); - digits.add(getMessage().getInt(DIGIT_5)); - digits.add(getMessage().getInt(DIGIT_6)); - digits.add(getMessage().getInt(DIGIT_7)); - digits.add(getMessage().getInt(DIGIT_8)); - digits.add(getMessage().getInt(DIGIT_9)); - digits.add(getMessage().getInt(DIGIT_10)); + digits.add(getInt(DIGIT_1)); + digits.add(getInt(DIGIT_2)); + digits.add(getInt(DIGIT_3)); + digits.add(getInt(DIGIT_4)); + digits.add(getInt(DIGIT_5)); + digits.add(getInt(DIGIT_6)); + digits.add(getInt(DIGIT_7)); + digits.add(getInt(DIGIT_8)); + digits.add(getInt(DIGIT_9)); + digits.add(getInt(DIGIT_10)); mTelephoneNumber = APCO25TelephoneNumber.createFrom(Digit.decode(digits)); } @@ -103,7 +99,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCTelephoneInterconnectVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCTelephoneInterconnectVoiceChannelUser.java index af22877ee..5bac2d781 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCTelephoneInterconnectVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCTelephoneInterconnectVoiceChannelUser.java @@ -1,49 +1,39 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - -import java.util.ArrayList; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.VoiceLinkControlMessage; +import java.util.Collections; import java.util.List; /** - * Current user of an APCO25 channel on both inbound and outbound channels + * Telephone interconnect voice channel user */ -public class LCTelephoneInterconnectVoiceChannelUser extends LinkControlWord +public class LCTelephoneInterconnectVoiceChannelUser extends VoiceLinkControlMessage { - private static final int[] RESERVED_1 = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] RESERVED_2 = {24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] CALL_TIMER = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - - private VoiceServiceOptions mVoiceServiceOptions; + private static final IntField CALL_TIMER = IntField.length16(OCTET_4_BIT_32); + private static final IntField ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mAddress; private List mIdentifiers; @@ -52,7 +42,7 @@ public class LCTelephoneInterconnectVoiceChannelUser extends LinkControlWord * * @param message */ - public LCTelephoneInterconnectVoiceChannelUser(BinaryMessage message) + public LCTelephoneInterconnectVoiceChannelUser(CorrectedBinaryMessage message) { super(message); } @@ -63,31 +53,18 @@ public String toString() sb.append(getMessageStub()); sb.append(" ID:").append(getAddress()); sb.append(" CALL TIMER:").append(getCallTimerDuration()).append("MS"); - sb.append(" ").append(getVoiceServiceOptions()); + sb.append(" ").append(getServiceOptions()); return sb.toString(); } - /** - * Service Options for this channel - */ - public VoiceServiceOptions getVoiceServiceOptions() - { - if(mVoiceServiceOptions == null) - { - mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); - } - - return mVoiceServiceOptions; - } - /** * Call timer duration in milliseconds. */ public long getCallTimerDuration() { //Convert from 100 millisecond intervals to milliseconds. - return getMessage().getInt(CALL_TIMER) * 100; + return getInt(CALL_TIMER) * 100; } /** @@ -97,7 +74,7 @@ public Identifier getAddress() { if(mAddress == null) { - mAddress = APCO25Talkgroup.create(getMessage().getInt(ADDRESS)); + mAddress = APCO25Talkgroup.create(getInt(ADDRESS)); } return mAddress; @@ -111,8 +88,7 @@ public List getIdentifiers() { if(mIdentifiers == null) { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getAddress()); + mIdentifiers = Collections.singletonList(getAddress()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitAuthenticationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitAuthenticationCommand.java index 0d1f0c5db..444c06932 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitAuthenticationCommand.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitAuthenticationCommand.java @@ -1,34 +1,30 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; 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.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - import java.util.ArrayList; import java.util.List; @@ -53,7 +49,7 @@ public class LCUnitAuthenticationCommand extends LinkControlWord * * @param message */ - public LCUnitAuthenticationCommand(BinaryMessage message) + public LCUnitAuthenticationCommand(CorrectedBinaryMessage message) { super(message); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitRegistrationCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitRegistrationCommand.java index 061668373..439b8e26c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitRegistrationCommand.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitRegistrationCommand.java @@ -1,51 +1,43 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; 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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - -import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** * Unit registration command + * Note: this message is obsolete as of TIA-102.AABF-A-1 */ public class LCUnitRegistrationCommand extends LinkControlWord { - private static final int[] WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; - private static final int[] SYSTEM_ID = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] TARGET_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63}; - private static final int[] RESERVED = {64, 65, 66, 67, 68, 69, 70, 71}; - - private Identifier mWACN; - private Identifier mSystem; - private Identifier mTargetAddress; + private static final IntField WACN = IntField.length20(OCTET_1_BIT_8); + private static final IntField SYSTEM_ID = IntField.length12(OCTET_3_BIT_24 + 4); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_5_BIT_40); + private FullyQualifiedRadioIdentifier mTargetAddress; private List mIdentifiers; /** @@ -53,7 +45,7 @@ public class LCUnitRegistrationCommand extends LinkControlWord * * @param message */ - public LCUnitRegistrationCommand(BinaryMessage message) + public LCUnitRegistrationCommand(CorrectedBinaryMessage message) { super(message); } @@ -62,46 +54,22 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - sb.append(" WACN:").append(getWACN()); - sb.append(" SYSTEM:").append(getSystem()); sb.append(" RADIO:").append(getTargetAddress()); return sb.toString(); } /** - * WACN - */ - public Identifier getWACN() - { - if(mWACN == null) - { - mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); - } - - return mWACN; - } - - /** - * System - */ - public Identifier getSystem() - { - if(mSystem == null) - { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID)); - } - - return mSystem; - } - - /** - * Source address + * Target address */ - public Identifier getTargetAddress() + public FullyQualifiedRadioIdentifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + //No persona ... reuse the radio ID. + int wacn = getInt(WACN); + int system = getInt(SYSTEM_ID); + int radio = getInt(TARGET_ADDRESS); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(radio, wacn, system, radio); } return mTargetAddress; @@ -115,10 +83,7 @@ public List getIdentifiers() { if(mIdentifiers == null) { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getWACN()); - mIdentifiers.add(getSystem()); - mIdentifiers.add(getTargetAddress()); + mIdentifiers = Collections.singletonList(getTargetAddress()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitAnswerRequest.java index 0f298f7b6..43b809140 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitAnswerRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitAnswerRequest.java @@ -1,49 +1,39 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - +import io.github.dsheirer.module.decode.p25.phase1.message.lc.VoiceLinkControlMessage; import java.util.ArrayList; import java.util.List; /** - * Current user of an APCO25 channel on both inbound and outbound channels + * Unit to unit answer request */ -public class LCUnitToUnitAnswerRequest extends LinkControlWord +public class LCUnitToUnitAnswerRequest extends VoiceLinkControlMessage { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - - private VoiceServiceOptions mVoiceServiceOptions; + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mTargetAddress; private Identifier mSourceAddress; private List mIdentifiers; @@ -53,7 +43,7 @@ public class LCUnitToUnitAnswerRequest extends LinkControlWord * * @param message */ - public LCUnitToUnitAnswerRequest(BinaryMessage message) + public LCUnitToUnitAnswerRequest(CorrectedBinaryMessage message) { super(message); } @@ -64,23 +54,10 @@ public String toString() sb.append(getMessageStub()); sb.append(" TO:").append(getTargetAddress()); sb.append(" FM:").append(getSourceAddress()); - sb.append(" ").append(getVoiceServiceOptions()); + sb.append(" ").append(getServiceOptions()); return sb.toString(); } - /** - * Service Options for this channel - */ - public VoiceServiceOptions getVoiceServiceOptions() - { - if(mVoiceServiceOptions == null) - { - mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); - } - - return mVoiceServiceOptions; - } - /** * Talkgroup address */ @@ -88,7 +65,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -101,7 +78,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS)); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUser.java index ab08a187b..99c56ccc4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUser.java @@ -1,48 +1,39 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.lc.standard; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - +import io.github.dsheirer.module.decode.p25.phase1.message.lc.VoiceLinkControlMessage; import java.util.ArrayList; import java.util.List; /** - * Current user of an APCO25 channel on both inbound and outbound channels + * Unit to Unit voice channel user */ -public class LCUnitToUnitVoiceChannelUser extends LinkControlWord +public class LCUnitToUnitVoiceChannelUser extends VoiceLinkControlMessage { - private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] TARGET_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - - private VoiceServiceOptions mVoiceServiceOptions; + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mTargetAddress; private Identifier mSourceAddress; private List mIdentifiers; @@ -52,7 +43,7 @@ public class LCUnitToUnitVoiceChannelUser extends LinkControlWord * * @param message */ - public LCUnitToUnitVoiceChannelUser(BinaryMessage message) + public LCUnitToUnitVoiceChannelUser(CorrectedBinaryMessage message) { super(message); } @@ -63,23 +54,10 @@ public String toString() sb.append(getMessageStub()); sb.append(" TO:").append(getTargetAddress()); sb.append(" FM:").append(getSourceAddress()); - sb.append(" ").append(getVoiceServiceOptions()); + sb.append(" ").append(getServiceOptions()); return sb.toString(); } - /** - * Service Options for this channel - */ - public VoiceServiceOptions getVoiceServiceOptions() - { - if(mVoiceServiceOptions == null) - { - mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS)); - } - - return mVoiceServiceOptions; - } - /** * Talkgroup address */ @@ -87,7 +65,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -100,7 +78,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS)); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUserExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUserExtended.java new file mode 100644 index 000000000..75671013b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUserExtended.java @@ -0,0 +1,108 @@ +/* + * ***************************************************************************** + * 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.lc.standard; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.ExtendedSourceLinkControlWord; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.List; + +/** + * Unit to Unit voice channel user with extended SUID + */ +public class LCUnitToUnitVoiceChannelUserExtended extends ExtendedSourceLinkControlWord implements IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_1_BIT_8); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_24); + + private VoiceServiceOptions mServiceOptions; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCUnitToUnitVoiceChannelUserExtended(CorrectedBinaryMessage message) + { + super(message); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + if(hasSourceIDExtension()) + { + sb.append(" FM:").append(getSourceAddress()); + } + sb.append(" ").append(getServiceOptions()); + return sb.toString(); + } + + public VoiceServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + /** + * Talkgroup address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + if(hasSourceIDExtension()) + { + mIdentifiers.add(getSourceAddress()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDU1Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDU1Message.java index 524c1f67c..9e7f85bbd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDU1Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDU1Message.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,12 +14,11 @@ * * 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.ldu; -import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.edac.Hamming10; @@ -30,12 +28,11 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWordFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * LDU Message is formatted as: @@ -180,7 +177,7 @@ private void createLinkControlWord() boolean irrecoverableErrors = REED_SOLOMON_24_12_13_P25.decode(input, output); //Transfer error corrected output to a new binary message - BinaryMessage binaryMessage = new BinaryMessage(72); + CorrectedBinaryMessage binaryMessage = new CorrectedBinaryMessage(72); int pointer = 0; @@ -199,11 +196,7 @@ private void createLinkControlWord() } mLinkControlWord = LinkControlWordFactory.create(binaryMessage); - - if(irrecoverableErrors) - { - mLinkControlWord.setValid(false); - } + mLinkControlWord.setValid(!irrecoverableErrors); //If we corrected any bit errors, update the original message with the bit error count for(int x = 0; x < 23; x++) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDUMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDUMessage.java index b1168be97..59675d91e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDUMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDUMessage.java @@ -20,14 +20,14 @@ package io.github.dsheirer.module.decode.p25.phase1.message.ldu; import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import java.util.ArrayList; import java.util.List; /** * LDU voice frame */ -public abstract class LDUMessage extends P25Message +public abstract class LDUMessage extends P25P1Message { public static final int IMBE_FRAME_1 = 0; public static final int IMBE_FRAME_2 = 144; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUHeader.java index 7b7a4cce8..ba658c313 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUHeader.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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; @@ -148,7 +145,7 @@ public Vendor getVendor() /** * Logical Link Identifier (ie TO radio identifier) */ - public Identifier getLLID() + public Identifier getTargetLLID() { if(mLLID == null) { @@ -187,7 +184,7 @@ public String toString() sb.append(getFormat().getLabel()); sb.append(isConfirmationRequired() ? " CONFIRMED" : " UNCONFIRMED"); sb.append(" VENDOR:").append(getVendor().getLabel()); - sb.append(isOutbound() ? "TO" : "FROM").append(" LLID").append(getLLID()); + sb.append(isOutbound() ? "TO" : "FROM").append(" LLID").append(getTargetLLID()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUMessage.java index 83416948d..e0a09072a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUMessage.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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; @@ -25,17 +22,16 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; import io.github.dsheirer.module.decode.p25.reference.Direction; import io.github.dsheirer.module.decode.p25.reference.PDUFormat; import io.github.dsheirer.module.decode.p25.reference.ServiceAccessPoint; import io.github.dsheirer.module.decode.p25.reference.Vendor; - import java.util.Collections; import java.util.List; -public class PDUMessage extends P25Message +public class PDUMessage extends P25P1Message { public static final int CONFIRMATION_REQUIRED_INDICATOR = 65; public static final int PACKET_DIRECTION_INDICATOR = 66; 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 3c2c4e901..8b8e25090 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 @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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; @@ -27,7 +24,7 @@ import io.github.dsheirer.edac.trellis.ViterbiDecoder_1_2_P25; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.P25P1Interleave; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCHeader; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCAuthenticationQuery; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCAuthenticationResponse; @@ -41,7 +38,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCStatusQueryResponse; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCStatusUpdateRequest; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitAcknowledgeResponse; -import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitToUnitVoiceServiceAnswerResponse; +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.AMBTCCallAlert; @@ -51,6 +48,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCGroupVoiceChannelGrant; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCIndividualDataChannelGrant; 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; @@ -74,11 +72,10 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.isp.UMBTCTelephoneInterconnectRequestExplicitDialing; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; import io.github.dsheirer.module.decode.p25.reference.PDUFormat; +import java.util.BitSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.BitSet; - public class PDUMessageFactory { private final static Logger mLog = LoggerFactory.getLogger(PDUMessageFactory.class); @@ -113,7 +110,7 @@ public static PDUSequence createPacketSequence(int nac, long timestamp, Correcte * @param pduSequence * @return */ - public static P25Message create(PDUSequence pduSequence, int nac, long timestamp) + public static P25P1Message create(PDUSequence pduSequence, int nac, long timestamp) { switch(pduSequence.getHeader().getFormat()) { @@ -156,7 +153,7 @@ public static DataBlock createUnconfirmedDataBlock(CorrectedBinaryMessage interl * @param timestamp of the packet sequence * @return packet data message parser */ - public static P25Message createPacketData(PDUSequence pduSequence, int nac, long timestamp) + public static P25P1Message createPacketData(PDUSequence pduSequence, int nac, long timestamp) { PacketHeader packetHeader = (PacketHeader)pduSequence.getHeader(); @@ -201,7 +198,7 @@ public static P25Message createPacketData(PDUSequence pduSequence, int nac, long * @param timestamp of the packet sequence * @return AMBTC message parser for the specific opcode */ - public static P25Message createAMBTC(PDUSequence pduSequence, int nac, long timestamp) + public static P25P1Message createAMBTC(PDUSequence pduSequence, int nac, long timestamp) { AMBTCHeader ambtcHeader = (AMBTCHeader)pduSequence.getHeader(); @@ -234,7 +231,7 @@ public static P25Message createAMBTC(PDUSequence pduSequence, int nac, long time case ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST: return new AMBTCUnitToUnitVoiceServiceRequest(pduSequence, nac, timestamp); case ISP_UNIT_TO_UNIT_ANSWER_RESPONSE: - return new AMBTCUnitToUnitVoiceServiceAnswerResponse(pduSequence, nac, timestamp); + return new AMBTCUnitToUnitAnswerResponse(pduSequence, nac, timestamp); case OSP_ADJACENT_STATUS_BROADCAST: return new AMBTCAdjacentStatusBroadcast(pduSequence, nac, timestamp); @@ -254,7 +251,7 @@ public static P25Message createAMBTC(PDUSequence pduSequence, int nac, long time return new AMBTCMessageUpdate(pduSequence, nac, timestamp); case OSP_NETWORK_STATUS_BROADCAST: return new AMBTCNetworkStatusBroadcast(pduSequence, nac, timestamp); - case OSP_PROTECTION_PARAMETER_BROADCAST: + case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN: return new AMBTCProtectionParameterBroadcast(pduSequence, nac, timestamp); case OSP_RFSS_STATUS_BROADCAST: return new AMBTCRFSSStatusBroadcast(pduSequence, nac, timestamp); @@ -278,6 +275,8 @@ public static P25Message createAMBTC(PDUSequence pduSequence, int nac, long time return new AMBTCUnitToUnitVoiceServiceChannelGrant(pduSequence, nac, timestamp); case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: return new AMBTCUnitToUnitVoiceServiceChannelGrantUpdate(pduSequence, nac, timestamp); + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT: + return new AMBTCMotorolaGroupRegroupChannelGrant(pduSequence, nac, timestamp); default: return new PDUSequenceMessage(pduSequence, nac, timestamp); } @@ -291,7 +290,7 @@ public static P25Message createAMBTC(PDUSequence pduSequence, int nac, long time * @param timestamp of the packet sequence * @return UMBTC message parser for the specific opcode */ - public static P25Message createUMBTC(PDUSequence pduSequence, int nac, long timestamp) + public static P25P1Message createUMBTC(PDUSequence pduSequence, int nac, long timestamp) { Opcode opcode = Opcode.OSP_UNKNOWN; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUSequenceMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUSequenceMessage.java index 714bf84ef..1d6b2dafd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUSequenceMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/PDUSequenceMessage.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,19 +14,18 @@ * * 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; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; - +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import java.util.Collections; import java.util.List; -public class PDUSequenceMessage extends P25Message +public class PDUSequenceMessage extends P25P1Message { private PDUSequence mPDUSequence; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/AMBTCMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/AMBTCMessage.java index 6228d0d86..fc4c5cdc0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/AMBTCMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/AMBTCMessage.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,25 +14,22 @@ * * 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; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.IBitErrorProvider; import io.github.dsheirer.module.decode.p25.P25Utils; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.DataBlock; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.UnconfirmedDataBlock; - import java.util.List; -public abstract class AMBTCMessage extends P25Message implements IBitErrorProvider +public abstract class AMBTCMessage extends P25P1Message implements IBitErrorProvider { protected static final int[] HEADER_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; @@ -129,53 +125,6 @@ public PDUSequence getPDUSequence() return mPDUSequence; } - private void extractMessage() - { - //There are 16 bits in the header - int length = 16; - - int blockCount = getPDUSequence().getHeader().getBlocksToFollowCount(); - - //Each block provides 108 bits/12 bytes and the final block uses 32-bits for CRC - length += (blockCount * 96); - - CorrectedBinaryMessage consolidatedMessage = new CorrectedBinaryMessage(length); - - //Transfer 2 octets from header - AMBTCHeader header = (AMBTCHeader)getPDUSequence().getHeader(); - - int dataOctetsValue = header.getDataOctets(); - consolidatedMessage.load(0, 16, dataOctetsValue); - - if(getPDUSequence().isComplete()) - { - int offset = 16; - for(DataBlock dataBlock: getPDUSequence().getDataBlocks()) - { - if(dataBlock instanceof UnconfirmedDataBlock) - { - BinaryMessage dataBlockMessage = dataBlock.getMessage(); - - for(int x = 0; x < dataBlockMessage.size(); x++) - { - if(dataBlockMessage.get(x)) - { - consolidatedMessage.set(x + offset); - } - } - - offset += dataBlockMessage.size(); - } - } - } - else - { - setValid(false); - } - - setMessage(consolidatedMessage); - } - @Override public int getBitsProcessedCount() { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/isp/AMBTCUnitToUnitVoiceServiceAnswerResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/isp/AMBTCUnitToUnitAnswerResponse.java similarity index 78% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/isp/AMBTCUnitToUnitVoiceServiceAnswerResponse.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/isp/AMBTCUnitToUnitAnswerResponse.java index 03751d0d6..33053fbaa 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/isp/AMBTCUnitToUnitVoiceServiceAnswerResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/isp/AMBTCUnitToUnitAnswerResponse.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.isp; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; 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.identifier.radio.APCO25RadioIdentifier; @@ -30,11 +28,10 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; import io.github.dsheirer.module.decode.p25.reference.AnswerResponse; import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; -public class AMBTCUnitToUnitVoiceServiceAnswerResponse extends AMBTCMessage +public class AMBTCUnitToUnitAnswerResponse extends AMBTCMessage implements IServiceOptionsProvider { private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; private static final int[] HEADER_ANSWER_RESPONSE = {72, 73, 74, 75, 76, 77, 78, 79}; @@ -53,7 +50,7 @@ public class AMBTCUnitToUnitVoiceServiceAnswerResponse extends AMBTCMessage private Identifier mTargetId; private List mIdentifiers; - public AMBTCUnitToUnitVoiceServiceAnswerResponse(PDUSequence PDUSequence, int nac, long timestamp) + public AMBTCUnitToUnitAnswerResponse(PDUSequence PDUSequence, int nac, long timestamp) { super(PDUSequence, nac, timestamp); } @@ -76,7 +73,7 @@ public String toString() { sb.append(" SYSTEM:").append(getSystem()); } - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } @@ -100,7 +97,7 @@ public AnswerResponse getAnswerResponse() return mAnswerResponse; } - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCCallAlert.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCCallAlert.java index 0c08b9a2b..ad0c64bea 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCCallAlert.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCCallAlert.java @@ -1,34 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; - import java.util.ArrayList; import java.util.List; @@ -37,16 +31,21 @@ */ public class AMBTCCallAlert extends AMBTCMessage { - private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; - private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 38, 39}; - - private Identifier mWacn; - private Identifier mSystem; - private Identifier mSourceId; - private Identifier mTargetAddress; + private static final int[] HEADER_SOURCE_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_SOURCE_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SOURCE_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_SOURCE_ADDRESS = {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_SOURCE_ADDRESS = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] BLOCK_1_TARGET_WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27}; + private static final int[] BLOCK_1_TARGET_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_1_TARGET_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; + + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; private List mIdentifiers; public AMBTCCallAlert(PDUSequence PDUSequence, int nac, long timestamp) @@ -58,86 +57,60 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - if(getSourceId() != null) + if(getSourceAddress() != null) { - sb.append(" FM:").append(getSourceId()); + sb.append(" FM:").append(getSourceAddress()); } if(getTargetAddress() != null) { sb.append(" TO:").append(getTargetAddress()); } - if(getWacn() != null) - { - sb.append(" WACN:").append(getWacn()); - } - if(getSystem() != null) - { - sb.append(" SYSTEM:").append(getSystem()); - } return sb.toString(); } - public Identifier getWacn() - { - if(mWacn == null && hasDataBlock(0)) - { - int value = getHeader().getMessage().getInt(HEADER_WACN); - value <<= 4; - value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); - mWacn = APCO25Wacn.create(value); - } - - return mWacn; - } - - public Identifier getSystem() + /** + * Source address + */ + public APCO25FullyQualifiedRadioIdentifier getSourceAddress() { - if(mSystem == null && hasDataBlock(0)) + if(mSourceAddress == null && hasDataBlock(0) && hasDataBlock(1)) { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + int localAddress = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ADDRESS) << 8; + localAddress += getDataBlock(1).getMessage().getInt(BLOCK_1_SOURCE_ADDRESS); + int wacn = getHeader().getMessage().getInt(HEADER_SOURCE_WACN) << 4; + wacn += getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, id); } - return mSystem; + return mSourceAddress; } - public Identifier getTargetAddress() + public APCO25FullyQualifiedRadioIdentifier getTargetAddress() { - if(mTargetAddress == null && hasDataBlock(0)) + if(mTargetAddress == null && hasDataBlock(1)) { - mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + int localAddress = getHeader().getMessage().getInt(HEADER_ADDRESS); + int wacn = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_WACN); + int system = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_SYSTEM); + int id = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(localAddress, wacn, system, id); } return mTargetAddress; } - public Identifier getSourceId() - { - if(mSourceId == null && hasDataBlock(0)) - { - mSourceId = APCO25RadioIdentifier.createFrom(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); - } - - return mSourceId; - } - @Override public List getIdentifiers() { if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - if(getWacn() != null) - { - mIdentifiers.add(getWacn()); - } - if(getSystem() != null) - { - mIdentifiers.add(getSystem()); - } - if(getSourceId() != null) + if(getSourceAddress() != null) { - mIdentifiers.add(getSourceId()); + mIdentifiers.add(getSourceAddress()); } if(getTargetAddress() != null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupAffiliationQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupAffiliationQuery.java index b7cd38fa8..067ac2755 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupAffiliationQuery.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupAffiliationQuery.java @@ -1,34 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.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.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; - import java.util.ArrayList; import java.util.List; @@ -43,9 +38,7 @@ public class AMBTCGroupAffiliationQuery extends AMBTCMessage private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private Identifier mWacn; - private Identifier mSystem; - private Identifier mSourceId; + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; private Identifier mTargetAddress; private List mIdentifiers; @@ -58,67 +51,41 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - if(getSourceId() != null) + if(getSourceAddress() != null) { - sb.append(" FM:").append(getSourceId()); + sb.append(" FM:").append(getSourceAddress()); } if(getTargetAddress() != null) { sb.append(" TO:").append(getTargetAddress()); } - if(getWacn() != null) - { - sb.append(" WACN:").append(getWacn()); - } - if(getSystem() != null) - { - sb.append(" SYSTEM:").append(getSystem()); - } return sb.toString(); } - public Identifier getWacn() - { - if(mWacn == null && hasDataBlock(0)) - { - int value = getHeader().getMessage().getInt(HEADER_WACN); - value <<= 4; - value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); - mWacn = APCO25Wacn.create(value); - } - - return mWacn; - } - - public Identifier getSystem() - { - if(mSystem == null && hasDataBlock(0)) - { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); - } - - return mSystem; - } - public Identifier getTargetAddress() { if(mTargetAddress == null && hasDataBlock(0)) { - mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getHeader().getMessage().getInt(HEADER_ADDRESS)); } return mTargetAddress; } - public Identifier getSourceId() + public Identifier getSourceAddress() { - if(mSourceId == null && hasDataBlock(0)) + if(mSourceAddress == null && hasDataBlock(0)) { - mSourceId = APCO25RadioIdentifier.createFrom(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + int wacn = getHeader().getMessage().getInt(HEADER_WACN); + wacn <<= 4; + wacn += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } - return mSourceId; + return mSourceAddress; } @Override @@ -127,17 +94,9 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - if(getWacn() != null) - { - mIdentifiers.add(getWacn()); - } - if(getSystem() != null) - { - mIdentifiers.add(getSystem()); - } - if(getSourceId() != null) + if(getSourceAddress() != null) { - mIdentifiers.add(getSourceId()); + mIdentifiers.add(getSourceAddress()); } if(getTargetAddress() != null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupAffiliationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupAffiliationResponse.java index 8052368b7..6ced43268 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupAffiliationResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupAffiliationResponse.java @@ -1,35 +1,33 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnnouncementTalkgroup; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FullyQualifiedTalkgroupIdentifier; 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.Response; import java.util.ArrayList; import java.util.List; @@ -38,22 +36,19 @@ */ public class AMBTCGroupAffiliationResponse extends AMBTCMessage { - private static final int[] HEADER_SOURCE_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] BLOCK_0_SOURCE_WACN = {0, 1, 2, 3}; - private static final int[] BLOCK_0_SOURCE_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + private static final int[] HEADER_GROUP_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_GROUP_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_GROUP_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_0_GROUP_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] BLOCK_0_GROUP_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[] BLOCK_0_GROUP_SYSTEM = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] BLOCK_0_GROUP_ID = {72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] BLOCK_0_ANNOUNCEMENT_GROUP_ID = {88, 89, 90, 91, 92, 93, 94, 95}; - private static final int[] BLOCK_1_ANNOUNCEMENT_GROUP_ID = {0, 1, 2, 3, 4, 5, 6, 7}; - - private Identifier mTargetAddress; - private APCO25FullyQualifiedRadioIdentifier mSourceId; - private APCO25FullyQualifiedTalkgroupIdentifier mGroupId; - private Identifier mAnnouncementGroupId; + private static final int[] BLOCK_0_ANNOUNCEMENT_GROUP_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] BLOCK_0_GROUP_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int BLOCK_0_LG = 64; + private static final int[] BLOCK_0_GAV = {70, 71}; + + private RadioIdentifier mTargetAddress; + private APCO25FullyQualifiedTalkgroupIdentifier mGroupAddress; + private TalkgroupIdentifier mAnnouncementGroup; private List mIdentifiers; public AMBTCGroupAffiliationResponse(PDUSequence PDUSequence, int nac, long timestamp) @@ -65,77 +60,69 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); + sb.append(" AFFILIATION ").append(getAffiliationResponse()); if(getTargetAddress() != null) { - sb.append(" TO:").append(getTargetAddress()); - } - if(getSourceId() != null) - { - sb.append(" FM:").append(getSourceId()); + sb.append(" FOR RADIO:").append(getTargetAddress()); } - if(getGroupId() != null) + if(getGroupAddress() != null) { - sb.append(" GROUP:").append(getGroupId()); + sb.append(" TO TALKGROUP:").append(getGroupAddress()); } - if(getAnnouncementGroupId() != null) + if(getAnnouncementGroup() != null) { - sb.append(" ANNOUNCEMENT GROUP:").append(getAnnouncementGroupId()); + sb.append(" ANNOUNCEMENT GROUP:").append(getAnnouncementGroup()); } return sb.toString(); } - public Identifier getTargetAddress() + /** + * Indicates the response status for the group affiliation request + */ + public Response getAffiliationResponse() { - if(mTargetAddress == null && hasDataBlock(0)) + if(hasDataBlock(0)) { - mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + return Response.fromValue(getDataBlock(0).getMessage().getInt(BLOCK_0_GAV)); } - return mTargetAddress; + return Response.UNKNOWN; } - public APCO25FullyQualifiedRadioIdentifier getSourceId() + public RadioIdentifier getTargetAddress() { - if(mSourceId == null && hasDataBlock(0)) + if(mTargetAddress == null) { - int wacn = getHeader().getMessage().getInt(HEADER_SOURCE_WACN); - wacn <<= 4; - wacn += getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); - int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM); - int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); - - mSourceId = APCO25FullyQualifiedRadioIdentifier.createFrom(wacn, system, id); + mTargetAddress = APCO25RadioIdentifier.createTo(getHeader().getMessage().getInt(HEADER_ADDRESS)); } - return mSourceId; + return mTargetAddress; } - public APCO25FullyQualifiedTalkgroupIdentifier getGroupId() + public APCO25FullyQualifiedTalkgroupIdentifier getGroupAddress() { - if(mGroupId == null && hasDataBlock(0)) + if(mGroupAddress == null && hasDataBlock(0)) { + int localAddress = getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_ADDRESS); int wacn = getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_WACN); int system = getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_SYSTEM); int id = getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_ID); - mGroupId = APCO25FullyQualifiedTalkgroupIdentifier.createAny(wacn, system, id); + mGroupAddress = APCO25FullyQualifiedTalkgroupIdentifier.createAny(localAddress, wacn, system, id); } - return mGroupId; + return mGroupAddress; } - public Identifier getAnnouncementGroupId() + public Identifier getAnnouncementGroup() { - if(mAnnouncementGroupId == null && hasDataBlock(0) && hasDataBlock(1)) + if(mAnnouncementGroup == null && hasDataBlock(0)) { - int id = getDataBlock(0).getMessage().getInt(BLOCK_0_ANNOUNCEMENT_GROUP_ID); - id <<= 8; - id += getDataBlock(1).getMessage().getInt(BLOCK_1_ANNOUNCEMENT_GROUP_ID); - - mAnnouncementGroupId = APCO25AnnouncementTalkgroup.create(id); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_ANNOUNCEMENT_GROUP_ADDRESS); + mAnnouncementGroup = APCO25AnnouncementTalkgroup.create(id); } - return mAnnouncementGroupId; + return mAnnouncementGroup; } @@ -149,17 +136,13 @@ public List getIdentifiers() { mIdentifiers.add(getTargetAddress()); } - if(getSourceId() != null) - { - mIdentifiers.add(getSourceId()); - } - if(getGroupId() != null) + if(getGroupAddress() != null) { - mIdentifiers.add(getGroupId()); + mIdentifiers.add(getGroupAddress()); } - if(getAnnouncementGroupId() != null) + if(getAnnouncementGroup() != null) { - mIdentifiers.add(getAnnouncementGroupId()); + mIdentifiers.add(getAnnouncementGroup()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupDataChannelGrant.java index dffe754c2..5bace0e6a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupDataChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupDataChannelGrant.java @@ -1,29 +1,27 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; 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.identifier.radio.APCO25RadioIdentifier; @@ -33,11 +31,10 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.UnconfirmedDataBlock; import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; - import java.util.ArrayList; import java.util.List; -public class AMBTCGroupDataChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +public class AMBTCGroupDataChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; @@ -67,11 +64,11 @@ public String toString() sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getGroupAddress()); sb.append(" CHAN:").append(getChannel()); - sb.append(" SERVICE OPTIONS:").append(getDataServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - public DataServiceOptions getDataServiceOptions() + public DataServiceOptions getServiceOptions() { if(mDataServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupVoiceChannelGrant.java index e41d993f7..30df8b494 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupVoiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCGroupVoiceChannelGrant.java @@ -24,6 +24,7 @@ import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; 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.identifier.radio.APCO25RadioIdentifier; @@ -37,7 +38,7 @@ import java.util.ArrayList; import java.util.List; -public class AMBTCGroupVoiceChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +public class AMBTCGroupVoiceChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; @@ -67,11 +68,11 @@ public String toString() sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getGroupAddress()); sb.append(" CHAN:").append(getChannel()); - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCIndividualDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCIndividualDataChannelGrant.java index 64d1f1d95..8130a04e6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCIndividualDataChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCIndividualDataChannelGrant.java @@ -1,50 +1,46 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.IChannelDescriptor; 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.identifier.radio.RadioIdentifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; 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.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; 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.phase1.message.pdu.block.UnconfirmedDataBlock; import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; - import java.util.ArrayList; import java.util.List; -public class AMBTCIndividualDataChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +public class AMBTCIndividualDataChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; - private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] BLOCK_0_SOURCE_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + private static final int[] BLOCK_0_SOURCE_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; private static final int[] BLOCK_0_SOURCE_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; private static final int[] BLOCK_0_TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, @@ -55,11 +51,8 @@ public class AMBTCIndividualDataChannelGrant extends AMBTCMessage implements IFr private static final int[] BLOCK_1_UPLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; private DataServiceOptions mDataServiceOptions; - private Identifier mWacn; - private Identifier mSystem; - private Identifier mSourceAddress; - private Identifier mSourceId; - private Identifier mTargetAddress; + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + private RadioIdentifier mTargetAddress; private List mIdentifiers; private APCO25Channel mChannel; private List mChannels; @@ -73,24 +66,16 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - if(getSourceId() != null) + if(getSourceAddress() != null) { - sb.append(" FM:").append(getSourceId()); + sb.append(" FM:").append(getSourceAddress()); } sb.append(" TO:").append(getTargetAddress()); - if(getWacn() != null) - { - sb.append(" WACN:").append(getWacn()); - } - if(getSystem() != null) - { - sb.append(" SYSTEM:").append(getSystem()); - } - sb.append(" SERVICE OPTIONS:").append(getDataServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - public DataServiceOptions getDataServiceOptions() + public DataServiceOptions getServiceOptions() { if(mDataServiceOptions == null) { @@ -100,51 +85,25 @@ public DataServiceOptions getDataServiceOptions() return mDataServiceOptions; } - public Identifier getWacn() + public APCO25FullyQualifiedRadioIdentifier getSourceAddress() { - if(mWacn == null && hasDataBlock(0)) + if(mSourceAddress == null && hasDataBlock(0)) { - mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); - } - - return mWacn; - } - - public Identifier getSystem() - { - if(mSystem == null && hasDataBlock(0)) - { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); - } - - return mSystem; - } - - public Identifier getSourceAddress() - { - if(mSourceAddress == null) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getHeader().getMessage().getInt(HEADER_ADDRESS)); + int localAddress = getHeader().getMessage().getInt(HEADER_ADDRESS); + int wacn = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, id); } return mSourceAddress; } - public Identifier getSourceId() - { - if(mSourceId == null && hasDataBlock(0)) - { - mSourceId = APCO25RadioIdentifier.createFrom(getHeader().getMessage().getInt(BLOCK_0_SOURCE_ID)); - } - - return mSourceId; - } - public Identifier getTargetAddress() { if(mTargetAddress == null && hasDataBlock(0)) { - mTargetAddress = APCO25Talkgroup.create(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ADDRESS)); } return mTargetAddress; @@ -164,18 +123,6 @@ public List getIdentifiers() { mIdentifiers.add(getTargetAddress()); } - if(getSourceId() != null) - { - mIdentifiers.add(getSourceId()); - } - if(getWacn() != null) - { - mIdentifiers.add(getWacn()); - } - if(getSystem() != null) - { - mIdentifiers.add(getSystem()); - } } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCMessageUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCMessageUpdate.java index 715a408d8..2bf329d51 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCMessageUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCMessageUpdate.java @@ -1,54 +1,52 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.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.identifier.message.APCO25ShortDataMessage; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; - import java.util.ArrayList; import java.util.List; /** - * Message update + * Message update - extended format */ public class AMBTCMessageUpdate extends AMBTCMessage { - private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; - private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] HEADER_SOURCE_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_SOURCE_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SOURCE_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; private static final int[] BLOCK_0_SDM = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - - private Identifier mWacn; - private Identifier mSystem; - private Identifier mSourceId; - private Identifier mTargetAddress; + private static final int[] BLOCK_0_SOURCE_ADDRESS = {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_SOURCE_ADDRESS = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] BLOCK_1_TARGET_WACN = {8, 9, 10, 11, 12, 13, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27}; + private static final int[] BLOCK_1_TARGET_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_1_TARGET_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; private Identifier mShortDataMessage; private List mIdentifiers; @@ -61,22 +59,14 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - if(getSourceId() != null) + if(getSourceAddress() != null) { - sb.append(" FM:").append(getSourceId()); + sb.append(" FM:").append(getSourceAddress()); } if(getTargetAddress() != null) { sb.append(" TO:").append(getTargetAddress()); } - if(getWacn() != null) - { - sb.append(" WACN:").append(getWacn()); - } - if(getSystem() != null) - { - sb.append(" SYSTEM:").append(getSystem()); - } if(getShortDataMessage() != null) { sb.append(" SHORT DATA MESSAGE:").append(getShortDataMessage()); @@ -85,47 +75,39 @@ public String toString() return sb.toString(); } - public Identifier getWacn() + public Identifier getTargetAddress() { - if(mWacn == null && hasDataBlock(0)) + if(mTargetAddress == null && hasDataBlock(1)) { - int value = getHeader().getMessage().getInt(HEADER_WACN); - value <<= 4; - value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); - mWacn = APCO25Wacn.create(value); + int localAddress = getHeader().getMessage().getInt(HEADER_ADDRESS); + int wacn = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_WACN); + int system = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_SYSTEM); + int id = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(localAddress, wacn, system, id); } - return mWacn; + return mTargetAddress; } - public Identifier getSystem() + public Identifier getSourceAddress() { - if(mSystem == null && hasDataBlock(0)) + if(mSourceAddress == null && hasDataBlock(0) && hasDataBlock(1)) { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); - } + int localAddress = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ADDRESS); + localAddress <<= 8; + localAddress += getDataBlock(1).getMessage().getInt(BLOCK_1_SOURCE_ADDRESS); - return mSystem; - } + int wacn = getHeader().getMessage().getInt(HEADER_SOURCE_WACN); + wacn <<= 4; + wacn += getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); - public Identifier getTargetAddress() - { - if(mTargetAddress == null && hasDataBlock(0)) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); - } - - return mTargetAddress; - } + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); - public Identifier getSourceId() - { - if(mSourceId == null && hasDataBlock(0)) - { - mSourceId = APCO25RadioIdentifier.createFrom(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, id); } - return mSourceId; + return mSourceAddress; } public Identifier getShortDataMessage() @@ -144,17 +126,9 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - if(getWacn() != null) - { - mIdentifiers.add(getWacn()); - } - if(getSystem() != null) - { - mIdentifiers.add(getSystem()); - } - if(getSourceId() != null) + if(getSourceAddress() != null) { - mIdentifiers.add(getSourceId()); + mIdentifiers.add(getSourceAddress()); } if(getTargetAddress() != null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCMotorolaGroupRegroupChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCMotorolaGroupRegroupChannelGrant.java new file mode 100644 index 000000000..e16022640 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCMotorolaGroupRegroupChannelGrant.java @@ -0,0 +1,181 @@ +/* + * ***************************************************************************** + * 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.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +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.phase1.message.pdu.block.UnconfirmedDataBlock; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola group regroup channel grant - explicit channel format + */ +public class AMBTCMotorolaGroupRegroupChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver, IServiceOptionsProvider +{ + private static final IntField HEADER_SERVICE_OPTIONS = IntField.length8(OCTET_8_BIT_64); + private static final IntField BLOCK_0_DOWNLINK_FREQUENCY_BAND = IntField.length4(OCTET_0_BIT_0); + private static final IntField BLOCK_0_DOWNLINK_CHANNEL_NUMBER = IntField.length12(OCTET_0_BIT_0 + 4); + private static final IntField BLOCK_0_UPLINK_FREQUENCY_BAND = IntField.length4(OCTET_2_BIT_16); + private static final IntField BLOCK_0_UPLINK_CHANNEL_NUMBER = IntField.length12(OCTET_2_BIT_16 + 4); + private static final IntField BLOCK_0_GROUP_ADDRESS = IntField.length16(OCTET_4_BIT_32); + + private VoiceServiceOptions mVoiceServiceOptions; + private APCO25Channel mChannel; + private Identifier mSourceAddress; + private Identifier mGroupAddress; + private List mIdentifiers; + private List mChannels; + + /** + * Constructs an instance + * @param PDUSequence containing a header and one data block. + * @param nac code + * @param timestamp for the sequence + */ + public AMBTCMotorolaGroupRegroupChannelGrant(PDUSequence PDUSequence, int nac, long timestamp) + { + super(PDUSequence, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getGroupAddress()); + sb.append(" CHAN:").append(getChannel()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); + return sb.toString(); + } + + public VoiceServiceOptions getServiceOptions() + { + if(mVoiceServiceOptions == null) + { + mVoiceServiceOptions = new VoiceServiceOptions(getHeader().getMessage().getInt(HEADER_SERVICE_OPTIONS)); + } + + return mVoiceServiceOptions; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getHeader().getMessage().getInt(HEADER_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null && hasDataBlock(0)) + { + mGroupAddress = APCO25Talkgroup.create(getDataBlock(0).getMessage().getInt(BLOCK_0_GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public boolean isExtendedChannel() + { + return hasDataBlock(0) && + (getDataBlock(0).getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER) != + getDataBlock(0).getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER)); + } + + public APCO25Channel 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)); + } + } + + if(mChannel == null) + { + mChannel = APCO25Channel.create(-1, 0); + } + } + + return mChannel; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + if(getSourceAddress() != null) + { + mIdentifiers.add(getSourceAddress()); + } + if(getGroupAddress() != null) + { + mIdentifiers.add(getGroupAddress()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + if(mChannels == null) + { + mChannels = new ArrayList<>(); + + if(getChannel() != null) + { + mChannels.add(getChannel()); + } + } + + return mChannels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCRoamingAddressResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCRoamingAddressResponse.java index 567f9fa88..d95e07384 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCRoamingAddressResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCRoamingAddressResponse.java @@ -1,34 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; - import java.util.ArrayList; import java.util.List; @@ -36,10 +30,7 @@ * Roaming address update message. Assigns WACN and SYSTEM combinations to a target address. * * Note: this is a variable block length message containing between 2 and 8 possible combination - * values. This class only parses out the first WACN an SYSTEM combination. Further parsing - * support is required to fully extract the additional combinations, but they are basically stacked - * one after another in each of the subsequent data blocks with the final data block containing the - * 4 CRC octets. + * values. */ public class AMBTCRoamingAddressResponse extends AMBTCMessage { @@ -48,10 +39,36 @@ public class AMBTCRoamingAddressResponse extends AMBTCMessage private static final int[] HEADER_WACN_A = {72, 73, 74, 75, 76, 77, 78, 79}; private static final int[] BLOCK_0_WACN_A = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; private static final int[] BLOCK_0_SYSTEM_A = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] BLOCK_0_WACN_B = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43}; + private static final int[] BLOCK_0_SYSTEM_B = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_WACN_C = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75}; + private static final int[] BLOCK_0_SYSTEM_C = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; + private static final int[] BLOCK_0_WACN_D = {88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_WACN_D = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + private static final int[] BLOCK_1_SYSTEM_D = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] BLOCK_1_WACN_E = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43}; + private static final int[] BLOCK_1_SYSTEM_E = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_2_WACN_F = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75}; + private static final int[] BLOCK_2_SYSTEM_F = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; + private static final int[] BLOCK_2_WACN_G = {88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_3_WACN_G = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + private static final int[] BLOCK_3_SYSTEM_G = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] BLOCK_3_WACN_H = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43}; + private static final int[] BLOCK_3_SYSTEM_H = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - private Identifier mWacn; - private Identifier mSystem; - private Identifier mTargetAddress; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressA; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressB; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressC; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressD; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressE; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressF; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressG; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressH; private List mIdentifiers; public AMBTCRoamingAddressResponse(PDUSequence PDUSequence, int nac, long timestamp) @@ -62,18 +79,38 @@ public AMBTCRoamingAddressResponse(PDUSequence PDUSequence, int nac, long timest public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(getMessageStub()); - if(getTargetAddress() != null) + sb.append(getMessageStub()).append(" ROAMING ADDRESSES"); + if(getRoamingAddressA() != null) + { + sb.append(" A:").append(getRoamingAddressA()); + } + if(getRoamingAddressB() != null) + { + sb.append(" B:").append(getRoamingAddressB()); + } + if(getRoamingAddressC() != null) + { + sb.append(" C:").append(getRoamingAddressC()); + } + if(getRoamingAddressD() != null) { - sb.append(" TO:").append(getTargetAddress()); + sb.append(" D:").append(getRoamingAddressD()); } - if(getWacn() != null) + if(getRoamingAddressE() != null) { - sb.append(" WACN A:").append(getWacn()); + sb.append(" E:").append(getRoamingAddressE()); } - if(getSystem() != null) + if(getRoamingAddressF() != null) { - sb.append(" SYSTEM A:").append(getSystem()); + sb.append(" F:").append(getRoamingAddressF()); + } + if(getRoamingAddressG() != null) + { + sb.append(" G:").append(getRoamingAddressG()); + } + if(getRoamingAddressH() != null) + { + sb.append(" H:").append(getRoamingAddressH()); } sb.append(" MSN:").append(getMessageSequenceNumber()); if(isLastMessage()) @@ -81,26 +118,15 @@ public String toString() sb.append(" (FINAL)"); } - sb.append(" TOTAL WACN/SYSTEM COMBOS IN MESSAGE:").append(getWacnCount()); - return sb.toString(); } - private int getWacnCount() + /** + * SU address that is used with all combinations of wacn.system as reported by the SU. + */ + private int getAddress() { - int dataBlockCount = getPDUSequence().getDataBlocks().size(); - - switch(dataBlockCount) - { - case 1: - return 2; - case 2: - return 5; - case 3: - return 8; - default: - return 2; - } + return getHeader().getMessage().getInt(HEADER_ADDRESS); } public boolean isLastMessage() @@ -113,37 +139,111 @@ private int getMessageSequenceNumber() return getHeader().getMessage().getInt(HEADER_MSN); } - public Identifier getWacn() + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressA() + { + if(mRoamingAddressA == null && hasDataBlock(0)) + { + int wacn = getHeader().getMessage().getInt(HEADER_WACN_A) << 12; + wacn += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN_A); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_A); + int id = getAddress(); + mRoamingAddressA = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mRoamingAddressA; + } + + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressB() { - if(mWacn == null && hasDataBlock(0)) + if(mRoamingAddressB == null && hasDataBlock(0)) { - int value = getHeader().getMessage().getInt(HEADER_WACN_A); - value <<= 12; - value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN_A); - mWacn = APCO25Wacn.create(value); + int wacn = getHeader().getMessage().getInt(BLOCK_0_WACN_B); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_B); + int id = getAddress(); + mRoamingAddressB = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } - return mWacn; + return mRoamingAddressB; } - public Identifier getSystem() + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressC() { - if(mSystem == null && hasDataBlock(0)) + if(mRoamingAddressC == null && hasDataBlock(0)) { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_A)); + int wacn = getHeader().getMessage().getInt(BLOCK_0_WACN_C); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_C); + int id = getAddress(); + mRoamingAddressC = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } - return mSystem; + return mRoamingAddressC; } - public Identifier getTargetAddress() + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressD() { - if(mTargetAddress == null && hasDataBlock(0)) + if(mRoamingAddressD == null && hasDataBlock(0) && hasDataBlock(1)) { - mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + int wacn = getDataBlock(0).getMessage().getInt(BLOCK_0_WACN_D) << 12; + wacn += getDataBlock(1).getMessage().getInt(BLOCK_1_WACN_D); + int system = getDataBlock(1).getMessage().getInt(BLOCK_1_SYSTEM_D); + int id = getAddress(); + mRoamingAddressD = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } - return mTargetAddress; + return mRoamingAddressD; + } + + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressE() + { + if(mRoamingAddressE == null && hasDataBlock(1)) + { + int wacn = getHeader().getMessage().getInt(BLOCK_1_WACN_E); + int system = getDataBlock(0).getMessage().getInt(BLOCK_1_SYSTEM_E); + int id = getAddress(); + mRoamingAddressE = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mRoamingAddressE; + } + + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressF() + { + if(mRoamingAddressF == null && hasDataBlock(2)) + { + int wacn = getHeader().getMessage().getInt(BLOCK_2_WACN_F); + int system = getDataBlock(0).getMessage().getInt(BLOCK_2_SYSTEM_F); + int id = getAddress(); + mRoamingAddressF = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mRoamingAddressF; + } + + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressG() + { + if(mRoamingAddressG == null && hasDataBlock(2) && hasDataBlock(3)) + { + int wacn = getDataBlock(0).getMessage().getInt(BLOCK_2_WACN_G) << 12; + wacn += getDataBlock(1).getMessage().getInt(BLOCK_3_WACN_G); + int system = getDataBlock(1).getMessage().getInt(BLOCK_3_SYSTEM_G); + int id = getAddress(); + mRoamingAddressG = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mRoamingAddressG; + } + + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressH() + { + if(mRoamingAddressH == null && hasDataBlock(3)) + { + int wacn = getHeader().getMessage().getInt(BLOCK_3_WACN_H); + int system = getDataBlock(0).getMessage().getInt(BLOCK_3_SYSTEM_H); + int id = getAddress(); + mRoamingAddressH = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mRoamingAddressH; } @Override @@ -152,17 +252,37 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - if(getWacn() != null) + if(getRoamingAddressA() != null) + { + mIdentifiers.add(getRoamingAddressA()); + } + if(getRoamingAddressB() != null) + { + mIdentifiers.add(getRoamingAddressB()); + } + if(getRoamingAddressC() != null) + { + mIdentifiers.add(getRoamingAddressC()); + } + if(getRoamingAddressD() != null) + { + mIdentifiers.add(getRoamingAddressD()); + } + if(getRoamingAddressE() != null) + { + mIdentifiers.add(getRoamingAddressE()); + } + if(getRoamingAddressF() != null) { - mIdentifiers.add(getWacn()); + mIdentifiers.add(getRoamingAddressF()); } - if(getSystem() != null) + if(getRoamingAddressG() != null) { - mIdentifiers.add(getSystem()); + mIdentifiers.add(getRoamingAddressG()); } - if(getTargetAddress() != null) + if(getRoamingAddressH() != null) { - mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getRoamingAddressH()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCRoamingAddressUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCRoamingAddressUpdate.java index f0a23b0d1..8f150bb54 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCRoamingAddressUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCRoamingAddressUpdate.java @@ -1,34 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.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.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; - import java.util.ArrayList; import java.util.List; @@ -48,10 +43,37 @@ public class AMBTCRoamingAddressUpdate extends AMBTCMessage private static final int[] HEADER_WACN_A = {72, 73, 74, 75, 76, 77, 78, 79}; private static final int[] BLOCK_0_WACN_A = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; private static final int[] BLOCK_0_SYSTEM_A = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] BLOCK_0_WACN_B = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43}; + private static final int[] BLOCK_0_SYSTEM_B = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_0_WACN_C = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75}; + private static final int[] BLOCK_0_SYSTEM_C = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; + private static final int[] BLOCK_0_WACN_D = {88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_WACN_D = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + private static final int[] BLOCK_1_SYSTEM_D = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] BLOCK_1_WACN_E = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43}; + private static final int[] BLOCK_1_SYSTEM_E = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final int[] BLOCK_2_WACN_F = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75}; + private static final int[] BLOCK_2_SYSTEM_F = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; + private static final int[] BLOCK_2_WACN_G = {88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_3_WACN_G = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + private static final int[] BLOCK_3_SYSTEM_G = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; + + //Source ID is in the same location in Blocks 1, 2, and 3 + private static final int[] SOURCE_ID = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47}; - private Identifier mWacn; - private Identifier mSystem; private Identifier mTargetAddress; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressA; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressB; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressC; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressD; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressE; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressF; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddressG; private List mIdentifiers; public AMBTCRoamingAddressUpdate(PDUSequence PDUSequence, int nac, long timestamp) @@ -67,13 +89,34 @@ public String toString() { sb.append(" TO:").append(getTargetAddress()); } - if(getWacn() != null) + sb.append(getMessageStub()).append(" ROAMING ADDRESSES"); + if(getRoamingAddressA() != null) + { + sb.append(" A:").append(getRoamingAddressA()); + } + if(getRoamingAddressB() != null) + { + sb.append(" B:").append(getRoamingAddressB()); + } + if(getRoamingAddressC() != null) + { + sb.append(" C:").append(getRoamingAddressC()); + } + if(getRoamingAddressD() != null) + { + sb.append(" D:").append(getRoamingAddressD()); + } + if(getRoamingAddressE() != null) + { + sb.append(" E:").append(getRoamingAddressE()); + } + if(getRoamingAddressF() != null) { - sb.append(" WACN A:").append(getWacn()); + sb.append(" F:").append(getRoamingAddressF()); } - if(getSystem() != null) + if(getRoamingAddressG() != null) { - sb.append(" SYSTEM A:").append(getSystem()); + sb.append(" G:").append(getRoamingAddressG()); } sb.append(" MSN:").append(getMessageSequenceNumber()); if(isLastMessage()) @@ -81,69 +124,139 @@ public String toString() sb.append(" (FINAL)"); } - sb.append(" TOTAL WACN/SYSTEM COMBOS IN MESSAGE:").append(getWacnCount()); - return sb.toString(); } - private int getWacnCount() + public boolean isLastMessage() { - int dataBlockCount = getPDUSequence().getDataBlocks().size(); + return getHeader().getMessage().get(LAST_MESSAGE_FLAG); + } - switch(dataBlockCount) + private int getMessageSequenceNumber() + { + return getHeader().getMessage().getInt(HEADER_MSN); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasDataBlock(0)) { - case 1: - return 2; - case 2: - return 5; - case 3: - return 8; - default: - return 2; + mTargetAddress = APCO25RadioIdentifier.createTo(getHeader().getMessage().getInt(HEADER_ADDRESS)); } + + return mTargetAddress; } - public boolean isLastMessage() + private int getAddress() { - return getHeader().getMessage().get(LAST_MESSAGE_FLAG); + if(hasDataBlock(3)) + { + return getDataBlock(3).getMessage().getInt(SOURCE_ID); + } + else if(hasDataBlock(2)) + { + return getDataBlock(2).getMessage().getInt(SOURCE_ID); + } + else if(hasDataBlock(1)) + { + return getDataBlock(1).getMessage().getInt(SOURCE_ID); + } + + return 0; } - private int getMessageSequenceNumber() + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressA() { - return getHeader().getMessage().getInt(HEADER_MSN); + if(mRoamingAddressA == null && hasDataBlock(0)) + { + int wacn = getHeader().getMessage().getInt(HEADER_WACN_A) << 12; + wacn += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN_A); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_A); + int id = getAddress(); + mRoamingAddressA = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mRoamingAddressA; } - public Identifier getWacn() + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressB() { - if(mWacn == null && hasDataBlock(0)) + if(mRoamingAddressB == null && hasDataBlock(0)) { - int value = getHeader().getMessage().getInt(HEADER_WACN_A); - value <<= 12; - value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN_A); - mWacn = APCO25Wacn.create(value); + int wacn = getHeader().getMessage().getInt(BLOCK_0_WACN_B); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_B); + int id = getAddress(); + mRoamingAddressB = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } - return mWacn; + return mRoamingAddressB; } - public Identifier getSystem() + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressC() { - if(mSystem == null && hasDataBlock(0)) + if(mRoamingAddressC == null && hasDataBlock(0)) { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_A)); + int wacn = getHeader().getMessage().getInt(BLOCK_0_WACN_C); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_C); + int id = getAddress(); + mRoamingAddressC = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } - return mSystem; + return mRoamingAddressC; } - public Identifier getTargetAddress() + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressD() { - if(mTargetAddress == null && hasDataBlock(0)) + if(mRoamingAddressD == null && hasDataBlock(0) && hasDataBlock(1)) { - mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + int wacn = getDataBlock(0).getMessage().getInt(BLOCK_0_WACN_D) << 12; + wacn += getDataBlock(1).getMessage().getInt(BLOCK_1_WACN_D); + int system = getDataBlock(1).getMessage().getInt(BLOCK_1_SYSTEM_D); + int id = getAddress(); + mRoamingAddressD = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } - return mTargetAddress; + return mRoamingAddressD; + } + + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressE() + { + if(mRoamingAddressE == null && hasDataBlock(1)) + { + int wacn = getHeader().getMessage().getInt(BLOCK_1_WACN_E); + int system = getDataBlock(0).getMessage().getInt(BLOCK_1_SYSTEM_E); + int id = getAddress(); + mRoamingAddressE = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mRoamingAddressE; + } + + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressF() + { + if(mRoamingAddressF == null && hasDataBlock(2)) + { + int wacn = getHeader().getMessage().getInt(BLOCK_2_WACN_F); + int system = getDataBlock(0).getMessage().getInt(BLOCK_2_SYSTEM_F); + int id = getAddress(); + mRoamingAddressF = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mRoamingAddressF; + } + + public APCO25FullyQualifiedRadioIdentifier getRoamingAddressG() + { + if(mRoamingAddressG == null && hasDataBlock(2) && hasDataBlock(3)) + { + int wacn = getDataBlock(0).getMessage().getInt(BLOCK_2_WACN_G) << 12; + wacn += getDataBlock(1).getMessage().getInt(BLOCK_3_WACN_G); + int system = getDataBlock(1).getMessage().getInt(BLOCK_3_SYSTEM_G); + int id = getAddress(); + mRoamingAddressG = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mRoamingAddressG; } @Override @@ -152,17 +265,33 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - if(getWacn() != null) + if(getRoamingAddressA() != null) + { + mIdentifiers.add(getRoamingAddressA()); + } + if(getRoamingAddressB() != null) + { + mIdentifiers.add(getRoamingAddressB()); + } + if(getRoamingAddressC() != null) + { + mIdentifiers.add(getRoamingAddressC()); + } + if(getRoamingAddressD() != null) + { + mIdentifiers.add(getRoamingAddressD()); + } + if(getRoamingAddressE() != null) { - mIdentifiers.add(getWacn()); + mIdentifiers.add(getRoamingAddressE()); } - if(getSystem() != null) + if(getRoamingAddressF() != null) { - mIdentifiers.add(getSystem()); + mIdentifiers.add(getRoamingAddressF()); } - if(getTargetAddress() != null) + if(getRoamingAddressG() != null) { - mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getRoamingAddressG()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCStatusQuery.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCStatusQuery.java index f0eaf98ed..56f82d6f7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCStatusQuery.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCStatusQuery.java @@ -1,34 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; - import java.util.ArrayList; import java.util.List; @@ -37,16 +31,21 @@ */ public class AMBTCStatusQuery extends AMBTCMessage { - private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; - private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] HEADER_SOURCE_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_SOURCE_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SOURCE_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - - private Identifier mWacn; - private Identifier mSystem; - private Identifier mSourceId; - private Identifier mTargetAddress; + private static final int[] BLOCK_0_SOURCE_ADDRESS = {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_SOURCE_ADDRESS = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] BLOCK_1_TARGET_WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27}; + private static final int[] BLOCK_1_TARGET_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_1_TARGET_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; + + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; private List mIdentifiers; public AMBTCStatusQuery(PDUSequence PDUSequence, int nac, long timestamp) @@ -58,67 +57,46 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - if(getSourceId() != null) + if(getSourceAddress() != null) { - sb.append(" FM:").append(getSourceId()); + sb.append(" FM:").append(getSourceAddress()); } if(getTargetAddress() != null) { sb.append(" TO:").append(getTargetAddress()); } - if(getWacn() != null) - { - sb.append(" WACN:").append(getWacn()); - } - if(getSystem() != null) - { - sb.append(" SYSTEM:").append(getSystem()); - } return sb.toString(); } - public Identifier getWacn() - { - if(mWacn == null && hasDataBlock(0)) - { - int value = getHeader().getMessage().getInt(HEADER_WACN); - value <<= 4; - value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); - mWacn = APCO25Wacn.create(value); - } - - return mWacn; - } - - public Identifier getSystem() + public APCO25FullyQualifiedRadioIdentifier getTargetAddress() { - if(mSystem == null && hasDataBlock(0)) + if(mTargetAddress == null && hasDataBlock(1)) { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); - } - - return mSystem; - } - - public Identifier getTargetAddress() - { - if(mTargetAddress == null && hasDataBlock(0)) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + int localAddress = getHeader().getMessage().getInt(HEADER_ADDRESS); + int wacn = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_WACN); + int system = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_SYSTEM); + int id = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(localAddress, wacn, system, id); } return mTargetAddress; } - public Identifier getSourceId() + public APCO25FullyQualifiedRadioIdentifier getSourceAddress() { - if(mSourceId == null && hasDataBlock(0)) + if(mSourceAddress == null) { - mSourceId = APCO25RadioIdentifier.createFrom(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + int localAddress = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ADDRESS) << 8; + localAddress += getDataBlock(1).getMessage().getInt(BLOCK_1_SOURCE_ADDRESS); + int wacn = getHeader().getMessage().getInt(HEADER_SOURCE_WACN) << 4; + wacn += getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, id); } - return mSourceId; + return mSourceAddress; } @Override @@ -127,17 +105,9 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - if(getWacn() != null) - { - mIdentifiers.add(getWacn()); - } - if(getSystem() != null) - { - mIdentifiers.add(getSystem()); - } - if(getSourceId() != null) + if(getSourceAddress() != null) { - mIdentifiers.add(getSourceId()); + mIdentifiers.add(getSourceAddress()); } if(getTargetAddress() != null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCStatusUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCStatusUpdate.java index a4489a0ee..dded919a3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCStatusUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCStatusUpdate.java @@ -1,56 +1,55 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UnitStatus; import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UserStatus; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage; - import java.util.ArrayList; import java.util.List; /** - * Status Query + * Status Update */ public class AMBTCStatusUpdate extends AMBTCMessage { - private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; - private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] HEADER_SOURCE_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_SOURCE_WACN = {0, 1, 2, 3}; + private static final int[] BLOCK_0_SOURCE_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; private static final int[] BLOCK_0_UNIT_STATUS = {40, 41, 42, 43, 44, 45, 46, 47}; private static final int[] BLOCK_0_USER_STATUS = {48, 49, 50, 51, 52, 53, 54, 55}; - - private Identifier mWacn; - private Identifier mSystem; - private Identifier mSourceId; - private Identifier mTargetAddress; + private static final int[] BLOCK_0_SOURCE_ADDRESS = {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; + private static final int[] BLOCK_1_SOURCE_ADDRESS = {0, 1, 2, 3, 4, 5, 6, 7}; + private static final int[] BLOCK_1_TARGET_WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27}; + private static final int[] BLOCK_1_TARGET_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_1_TARGET_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; + + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; private Identifier mUnitStatus; private Identifier mUserStatus; private List mIdentifiers; @@ -68,17 +67,9 @@ public String toString() { sb.append(" TO:").append(getTargetAddress()); } - if(getSourceId() != null) - { - sb.append(" FM:").append(getSourceId()); - } - if(getWacn() != null) - { - sb.append(" WACN:").append(getWacn()); - } - if(getSystem() != null) + if(getSourceAddress() != null) { - sb.append(" SYSTEM:").append(getSystem()); + sb.append(" FM:").append(getSourceAddress()); } if(getUnitStatus() != null) { @@ -92,47 +83,36 @@ public String toString() return sb.toString(); } - public Identifier getWacn() - { - if(mWacn == null && hasDataBlock(0)) - { - int value = getHeader().getMessage().getInt(HEADER_WACN); - value <<= 4; - value += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); - mWacn = APCO25Wacn.create(value); - } - - return mWacn; - } - - public Identifier getSystem() - { - if(mSystem == null && hasDataBlock(0)) - { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); - } - - return mSystem; - } - public Identifier getTargetAddress() { if(mTargetAddress == null && hasDataBlock(0)) { - mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + int localAddress = getHeader().getMessage().getInt(HEADER_ADDRESS); + int wacn = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_WACN); + int system = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_SYSTEM); + int id = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(localAddress, wacn, system, id); } return mTargetAddress; } - public Identifier getSourceId() + public Identifier getSourceAddress() { - if(mSourceId == null && hasDataBlock(0)) + if(mSourceAddress == null && hasDataBlock(0) && hasDataBlock(1)) { - mSourceId = APCO25RadioIdentifier.createFrom(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID)); + int wacn = getHeader().getMessage().getInt(HEADER_SOURCE_WACN); + wacn <<= 4; + wacn += getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); + int localAddress = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ADDRESS) << 8; + localAddress += getDataBlock(1).getMessage().getInt(BLOCK_1_SOURCE_ADDRESS); + + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, id); } - return mSourceId; + return mSourceAddress; } public Identifier getUnitStatus() @@ -161,21 +141,21 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - if(getWacn() != null) + if(getSourceAddress() != null) { - mIdentifiers.add(getWacn()); + mIdentifiers.add(getSourceAddress()); } - if(getSystem() != null) + if(getTargetAddress() != null) { - mIdentifiers.add(getSystem()); + mIdentifiers.add(getTargetAddress()); } - if(getSourceId() != null) + if(getUserStatus() != null) { - mIdentifiers.add(getSourceId()); + mIdentifiers.add(getUserStatus()); } - if(getTargetAddress() != null) + if(getUnitStatus() != null) { - mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getUnitStatus()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrant.java index f3b43be12..6fe8f9f77 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrant.java @@ -24,6 +24,7 @@ import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; 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.identifier.radio.APCO25RadioIdentifier; @@ -36,7 +37,7 @@ import java.util.ArrayList; import java.util.List; -public class AMBTCTelephoneInterconnectChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +public class AMBTCTelephoneInterconnectChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; @@ -68,11 +69,11 @@ public String toString() sb.append(" CHAN:").append(getChannel()); } sb.append(" CALL TIMER:").append(getCallTimer()).append("ms"); - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrantUpdate.java index 401922f23..5d55ce41f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrantUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCTelephoneInterconnectChannelGrantUpdate.java @@ -24,6 +24,7 @@ import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; 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.identifier.radio.APCO25RadioIdentifier; @@ -36,7 +37,7 @@ import java.util.ArrayList; import java.util.List; -public class AMBTCTelephoneInterconnectChannelGrantUpdate extends AMBTCMessage implements IFrequencyBandReceiver +public class AMBTCTelephoneInterconnectChannelGrantUpdate extends AMBTCMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; @@ -68,11 +69,11 @@ public String toString() sb.append(" CHAN:").append(getChannel()); } sb.append(" CALL TIMER:").append(getCallTimer()).append("ms"); - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitRegistrationResponse.java index adc93af4a..b9f457925 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitRegistrationResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitRegistrationResponse.java @@ -1,34 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; 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.Response; - import java.util.ArrayList; import java.util.List; @@ -40,16 +35,12 @@ public class AMBTCUnitRegistrationResponse extends AMBTCMessage private static final int[] HEADER_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; private static final int[] BLOCK_0_WACN = {0, 1, 2, 3}; private static final int[] BLOCK_0_SYSTEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] BLOCK_0_SOURCE_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + private static final int[] BLOCK_0_RADIO_ID = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] BLOCK_0_SOURCE_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, - 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] BLOCK_0_RESPONSE = {70, 71}; + private static final int[] BLOCK_0_RESPONSE = {46, 47}; private Response mResponse; - private Identifier mTargetAddress; - private APCO25FullyQualifiedRadioIdentifier mFullyQualifiedTargetAddress; - private Identifier mSourceAddress; + private APCO25FullyQualifiedRadioIdentifier mRegistrationAddress; private List mIdentifiers; public AMBTCUnitRegistrationResponse(PDUSequence PDUSequence, int nac, long timestamp) @@ -62,19 +53,10 @@ public String toString() StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); sb.append("REGISTRATION ").append(getResponse()); - if(getFullyQualifiedTargetAddress() != null) - { - sb.append(" TO:").append(getFullyQualifiedTargetAddress()); - } - if(getTargetAddress() != null) + if(getRegistrationAddress() != null) { - sb.append(" ALIASED AS:").append(getTargetAddress()); + sb.append(" FOR:").append(getRegistrationAddress()); } - if(getSourceAddress() != null) - { - sb.append(" FROM:").append(getSourceAddress()); - } - return sb.toString(); } @@ -88,40 +70,21 @@ public Response getResponse() return mResponse; } - public Identifier getTargetAddress() - { - if(mTargetAddress == null && hasDataBlock(0)) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); - } - - return mTargetAddress; - } - - public Identifier getFullyQualifiedTargetAddress() + public Identifier getRegistrationAddress() { - if(mFullyQualifiedTargetAddress == null && hasDataBlock(0)) + if(mRegistrationAddress == null && hasDataBlock(0)) { + int localAddress = getHeader().getMessage().getInt(HEADER_ADDRESS); int wacn = getHeader().getMessage().getInt(HEADER_WACN); wacn <<= 4; wacn += getDataBlock(0).getMessage().getInt(BLOCK_0_WACN); int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM); - int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_RADIO_ID); - mFullyQualifiedTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(wacn, system, id); + mRegistrationAddress = APCO25FullyQualifiedRadioIdentifier.createTo(localAddress, wacn, system, id); } - return mFullyQualifiedTargetAddress; - } - - public Identifier getSourceAddress() - { - if(mSourceAddress == null && hasDataBlock(0)) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ADDRESS)); - } - - return mSourceAddress; + return mRegistrationAddress; } @Override @@ -130,17 +93,10 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - if(getFullyQualifiedTargetAddress() != null) - { - mIdentifiers.add(getFullyQualifiedTargetAddress()); - } - if(getTargetAddress() != null) - { - mIdentifiers.add(getTargetAddress()); - } - if(getSourceAddress() != null) + + if(getRegistrationAddress() != null) { - mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getRegistrationAddress()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitAnswerRequest.java index a5472473e..fa563508e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitAnswerRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitAnswerRequest.java @@ -1,53 +1,47 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.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.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; 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.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; -public class AMBTCUnitToUnitAnswerRequest extends AMBTCMessage +public class AMBTCUnitToUnitAnswerRequest extends AMBTCMessage implements IServiceOptionsProvider { private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; private static final int[] BLOCK_0_RESERVED = {0, 1, 2, 3, 4, 5, 6, 7}; - private static final int[] BLOCK_0_WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; - private static final int[] BLOCK_0_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_0_SOURCE_WACN = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27}; + private static final int[] BLOCK_0_SOURCE_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; private static final int[] BLOCK_0_SOURCE_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; private VoiceServiceOptions mVoiceServiceOptions; - private Identifier mWacn; - private Identifier mSystem; - private Identifier mSourceId; + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; private Identifier mTargetAddress; private List mIdentifiers; @@ -60,24 +54,16 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - if(getSourceId() != null) + if(getSourceAddress() != null) { - sb.append(" FM:").append(getSourceId()); + sb.append(" FM:").append(getSourceAddress()); } sb.append(" TO:").append(getTargetAddress()); - if(getWacn() != null) - { - sb.append(" WACN:").append(getWacn()); - } - if(getSystem() != null) - { - sb.append(" SYSTEM:").append(getSystem()); - } - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { @@ -87,41 +73,24 @@ public VoiceServiceOptions getVoiceServiceOptions() return mVoiceServiceOptions; } - public Identifier getWacn() - { - if(mWacn == null && hasDataBlock(0)) - { - mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); - } - - return mWacn; - } - - public Identifier getSystem() + public APCO25FullyQualifiedRadioIdentifier getSourceAddress() { - if(mSystem == null && hasDataBlock(0)) + if(mSourceAddress == null && hasDataBlock(0)) { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); + int wacn = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } - return mSystem; - } - - public Identifier getSourceId() - { - if(mSourceId == null) - { - mSourceId = APCO25RadioIdentifier.createFrom(getHeader().getMessage().getInt(BLOCK_0_SOURCE_ID)); - } - - return mSourceId; + return mSourceAddress; } public Identifier getTargetAddress() { - if(mTargetAddress == null && hasDataBlock(0)) + if(mTargetAddress == null) { - mTargetAddress = APCO25Talkgroup.create(getDataBlock(0).getMessage().getInt(HEADER_ADDRESS)); + mTargetAddress = APCO25RadioIdentifier.createTo(getHeader().getMessage().getInt(HEADER_ADDRESS)); } return mTargetAddress; @@ -137,17 +106,9 @@ public List getIdentifiers() { mIdentifiers.add(getTargetAddress()); } - if(getSourceId() != null) - { - mIdentifiers.add(getSourceId()); - } - if(getWacn() != null) - { - mIdentifiers.add(getWacn()); - } - if(getSystem() != null) + if(getSourceAddress() != null) { - mIdentifiers.add(getSystem()); + mIdentifiers.add(getSourceAddress()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrant.java index 42517c9cb..bbd53d72f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrant.java @@ -1,50 +1,45 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.IChannelDescriptor; 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.IServiceOptionsProvider; 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.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; 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.phase1.message.pdu.block.UnconfirmedDataBlock; import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; -public class AMBTCUnitToUnitVoiceServiceChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver +public class AMBTCUnitToUnitVoiceServiceChannelGrant extends AMBTCMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; - private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] HEADER_TARGET_WACN = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_SOURCE_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19}; + private static final int[] BLOCK_0_SOURCE_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; private static final int[] BLOCK_0_SOURCE_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; private static final int[] BLOCK_0_TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, @@ -53,13 +48,14 @@ public class AMBTCUnitToUnitVoiceServiceChannelGrant extends AMBTCMessage implem private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; private static final int[] BLOCK_1_UPLINK_FREQUENCY_BAND = {0, 1, 2, 3}; private static final int[] BLOCK_1_UPLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_1_TARGET_WACN = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + private static final int[] BLOCK_1_TARGET_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_1_TARGET_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; private VoiceServiceOptions mVoiceServiceOptions; - private Identifier mWacn; - private Identifier mSystem; - private Identifier mSourceAddress; - private Identifier mSourceId; - private Identifier mTargetAddress; + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; private List mIdentifiers; private APCO25Channel mChannel; private List mChannels; @@ -73,24 +69,20 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - if(getSourceId() != null) - { - sb.append(" FM:").append(getSourceId()); - } - sb.append(" TO:").append(getTargetAddress()); - if(getWacn() != null) + if(getSourceAddress() != null) { - sb.append(" WACN:").append(getWacn()); + sb.append(" FM:").append(getSourceAddress()); } - if(getSystem() != null) + if(getTargetAddress() != null) { - sb.append(" SYSTEM:").append(getSystem()); + sb.append(" TO:").append(getTargetAddress()); } - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" CHANNEL:").append(getChannel()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { @@ -100,51 +92,30 @@ public VoiceServiceOptions getVoiceServiceOptions() return mVoiceServiceOptions; } - public Identifier getWacn() - { - if(mWacn == null && hasDataBlock(0)) - { - mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); - } - - return mWacn; - } - - public Identifier getSystem() - { - if(mSystem == null && hasDataBlock(0)) - { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); - } - - return mSystem; - } - - public Identifier getSourceAddress() + public APCO25FullyQualifiedRadioIdentifier getSourceAddress() { - if(mSourceAddress == null) + if(mSourceAddress == null && hasDataBlock(0)) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getHeader().getMessage().getInt(HEADER_ADDRESS)); + int localAddress = getHeader().getMessage().getInt(HEADER_ADDRESS); + int wacn = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, id); } return mSourceAddress; } - public Identifier getSourceId() - { - if(mSourceId == null && hasDataBlock(0)) - { - mSourceId = APCO25RadioIdentifier.createFrom(getHeader().getMessage().getInt(BLOCK_0_SOURCE_ID)); - } - - return mSourceId; - } - public Identifier getTargetAddress() { - if(mTargetAddress == null && hasDataBlock(0)) + if(mTargetAddress == null && hasDataBlock(0) && hasDataBlock(1)) { - mTargetAddress = APCO25Talkgroup.create(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ADDRESS)); + int localAddress = getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ADDRESS); + int wacn = getHeader().getMessage().getInt(HEADER_TARGET_WACN) << 12; + wacn += getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_WACN); + int system = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_SYSTEM); + int id = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(localAddress, wacn, system, id); } return mTargetAddress; @@ -164,17 +135,9 @@ public List getIdentifiers() { mIdentifiers.add(getTargetAddress()); } - if(getSourceId() != null) - { - mIdentifiers.add(getSourceId()); - } - if(getWacn() != null) - { - mIdentifiers.add(getWacn()); - } - if(getSystem() != null) + if(getChannel() != null) { - mIdentifiers.add(getSystem()); + mIdentifiers.add(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrantUpdate.java index 044c62be0..f304beb70 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrantUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/ambtc/osp/AMBTCUnitToUnitVoiceServiceChannelGrantUpdate.java @@ -1,50 +1,44 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.IChannelDescriptor; 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.IServiceOptionsProvider; 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.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; 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.phase1.message.pdu.block.UnconfirmedDataBlock; import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; -public class AMBTCUnitToUnitVoiceServiceChannelGrantUpdate extends AMBTCMessage implements IFrequencyBandReceiver +public class AMBTCUnitToUnitVoiceServiceChannelGrantUpdate extends AMBTCMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] HEADER_SERVICE_OPTIONS = {64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] HEADER_RESERVED = {72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] BLOCK_0_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; - private static final int[] BLOCK_0_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] HEADER_TARGET_WACN = {72, 73, 74, 75, 76, 77, 78, 79}; + private static final int[] BLOCK_0_SOURCE_WACN = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + private static final int[] BLOCK_0_SOURCE_SYSTEM = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; private static final int[] BLOCK_0_SOURCE_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; private static final int[] BLOCK_0_TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, @@ -53,13 +47,14 @@ public class AMBTCUnitToUnitVoiceServiceChannelGrantUpdate extends AMBTCMessage private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; private static final int[] BLOCK_1_UPLINK_FREQUENCY_BAND = {0, 1, 2, 3}; private static final int[] BLOCK_1_UPLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] BLOCK_1_TARGET_WACN = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + private static final int[] BLOCK_1_TARGET_SYSTEM = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] BLOCK_1_TARGET_ID = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63}; private VoiceServiceOptions mVoiceServiceOptions; - private Identifier mWacn; - private Identifier mSystem; - private Identifier mSourceAddress; - private Identifier mSourceId; - private Identifier mTargetAddress; + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; private List mIdentifiers; private APCO25Channel mChannel; private List mChannels; @@ -73,24 +68,23 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - if(getSourceId() != null) + if(getSourceAddress() != null) { - sb.append(" FM:").append(getSourceId()); + sb.append(" FM:").append(getSourceAddress()); } - sb.append(" TO:").append(getTargetAddress()); - if(getWacn() != null) + if(getTargetAddress() != null) { - sb.append(" WACN:").append(getWacn()); + sb.append(" TO:").append(getTargetAddress()); } - if(getSystem() != null) + if(getChannel() != null) { - sb.append(" SYSTEM:").append(getSystem()); + sb.append(" CHAN:").append(getChannel()); } - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { @@ -100,51 +94,30 @@ public VoiceServiceOptions getVoiceServiceOptions() return mVoiceServiceOptions; } - public Identifier getWacn() - { - if(mWacn == null && hasDataBlock(0)) - { - mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN)); - } - - return mWacn; - } - - public Identifier getSystem() - { - if(mSystem == null && hasDataBlock(0)) - { - mSystem = APCO25System.create(getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM)); - } - - return mSystem; - } - - public Identifier getSourceAddress() + public APCO25FullyQualifiedRadioIdentifier getSourceAddress() { - if(mSourceAddress == null) + if(mSourceAddress == null && hasDataBlock(0)) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getHeader().getMessage().getInt(HEADER_ADDRESS)); + int localAddress = getHeader().getMessage().getInt(HEADER_ADDRESS); + int wacn = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_WACN); + int system = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_SYSTEM); + int id = getDataBlock(0).getMessage().getInt(BLOCK_0_SOURCE_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, id); } return mSourceAddress; } - public Identifier getSourceId() - { - if(mSourceId == null && hasDataBlock(0)) - { - mSourceId = APCO25RadioIdentifier.createFrom(getHeader().getMessage().getInt(BLOCK_0_SOURCE_ID)); - } - - return mSourceId; - } - - public Identifier getTargetAddress() + public APCO25FullyQualifiedRadioIdentifier getTargetAddress() { - if(mTargetAddress == null && hasDataBlock(0)) + if(mTargetAddress == null && hasDataBlock(0) && hasDataBlock(1)) { - mTargetAddress = APCO25Talkgroup.create(getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ADDRESS)); + int localAddress = getDataBlock(0).getMessage().getInt(BLOCK_0_TARGET_ADDRESS); + int wacn = getHeader().getMessage().getInt(HEADER_TARGET_WACN) << 12; + wacn += getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_WACN); + int system = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_SYSTEM); + int id = getDataBlock(1).getMessage().getInt(BLOCK_1_TARGET_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, id); } return mTargetAddress; @@ -164,17 +137,9 @@ public List getIdentifiers() { mIdentifiers.add(getTargetAddress()); } - if(getSourceId() != null) - { - mIdentifiers.add(getSourceId()); - } - if(getWacn() != null) - { - mIdentifiers.add(getWacn()); - } - if(getSystem() != null) + if(getChannel() != null) { - mIdentifiers.add(getSystem()); + mIdentifiers.add(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketHeader.java index fd8343b55..eadcee7e3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketHeader.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.packet; @@ -53,7 +52,7 @@ public String toString() sb.append(" FMT:").append(getFormat().getLabel()); sb.append(" SAP:").append(getServiceAccessPoint().name()); sb.append(" VEND:").append(getVendor().getLabel()); - sb.append(isOutbound() ? "TO" : "FROM").append(" LLID:").append(getLLID()); + sb.append(isOutbound() ? "TO" : "FROM").append(" LLID:").append(getTargetLLID()); sb.append(" BLKS TO FOLLOW:").append(getBlocksToFollowCount()); if(isSynchronize()) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketMessage.java index 6acf89672..23bf045fa 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/PacketMessage.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.packet; @@ -27,21 +26,20 @@ import io.github.dsheirer.module.decode.ip.PacketMessageFactory; import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.ConfirmedDataBlock; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.DataBlock; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketHeader; import io.github.dsheirer.module.decode.p25.reference.IPHeaderCompression; import io.github.dsheirer.module.decode.p25.reference.UDPHeaderCompression; - import java.util.ArrayList; import java.util.List; /** * Packet Data Unit (PDU) sequence containing IP packet data. */ -public class PacketMessage extends P25Message +public class PacketMessage extends P25P1Message { // private final static Logger mLog = LoggerFactory.getLogger(PacketMessage.class); @@ -111,7 +109,7 @@ public String toString() if(getPacket() instanceof IPV4Packet) { - sb.append(" LLID:").append(getHeader().getLLID()); + sb.append(" LLID:").append(getHeader().getTargetLLID()); SNDCPPacketHeader sndcpPacketHeader = getSNDCPPacketHeader(); sb.append(" NSAPI:").append(sndcpPacketHeader.getNSAPI()); @@ -264,7 +262,7 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getPDUSequence().getHeader().getLLID()); + mIdentifiers.add(getHeader().getTargetLLID()); if(getPacket() != null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/sndcp/SNDCPPacketMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/sndcp/SNDCPPacketMessage.java index eecfba365..2e3820a9c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/sndcp/SNDCPPacketMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/sndcp/SNDCPPacketMessage.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.packet.sndcp; @@ -38,7 +37,7 @@ public String toString() StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - sb.append(" LLID:").append(getHeader().getLLID()); + sb.append(" LLID:").append(getHeader().getTargetLLID()); if(getPDUSequence().isComplete()) { sb.append(" ").append(getSNDCPMessage().toString()); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/response/ResponseHeader.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/response/ResponseHeader.java index f2fb75826..195f87885 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/response/ResponseHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/response/ResponseHeader.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,11 +14,13 @@ * * 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.response; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUHeader; import io.github.dsheirer.module.decode.p25.reference.PacketResponse; import io.github.dsheirer.module.decode.p25.reference.Vendor; @@ -28,7 +29,8 @@ public class ResponseHeader extends PDUHeader { public static final int[] RESPONSE = {8,9,10,11,12,13,14,15}; public static final int SOURCE_LLID_FLAG = 48; - public static final int[] FROM_LOGICAL_LINK_ID = {56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79}; + public static final int[] SOURCE_LOGICAL_LINK_ID = {56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79}; + private Identifier mSourceLLID; public ResponseHeader(CorrectedBinaryMessage message, boolean passesCRC) { @@ -45,14 +47,14 @@ public String toString() sb.append("***CRC-FAIL*** "); } - sb.append("PDU RESPONSE"); + sb.append("PDU RESPONSE"); if(hasSourceLLID()) { - sb.append(" FROM:").append(getFromLogicalLinkID()); + sb.append(" FROM:").append(getSourceLLID()); } - sb.append(" TO:").append(getLLID()); + sb.append(" TO:").append(getTargetLLID()); sb.append(" ").append(getResponse()); @@ -85,8 +87,13 @@ public boolean hasSourceLLID() /** * Source Logical Link Identifier (ie FROM radio identifier) */ - public String getFromLogicalLinkID() + public Identifier getSourceLLID() { - return getMessage().getHex(FROM_LOGICAL_LINK_ID, 6); + if(mSourceLLID == null) + { + mSourceLLID = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_LOGICAL_LINK_ID)); + } + + return mSourceLLID; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/response/ResponseMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/response/ResponseMessage.java index 706efeee1..868bd5e9e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/response/ResponseMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/response/ResponseMessage.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -20,17 +20,28 @@ package io.github.dsheirer.module.decode.p25.phase1.message.pdu.response; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequenceMessage; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.DataBlock; import io.github.dsheirer.module.decode.p25.reference.PacketResponse; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +/** + * Response message + */ public class ResponseMessage extends PDUSequenceMessage { + private List mIdentifiers; + + /** + * Construct an instance + * @param PDUSequence containing the response message + * @param nac value + * @param timestamp for the message + */ public ResponseMessage(PDUSequence PDUSequence, int nac, long timestamp) { super(PDUSequence, nac, timestamp); @@ -46,8 +57,28 @@ public String toString() StringBuilder sb = new StringBuilder(); sb.append("NAC:").append(getNAC()); + if(!isValid()) + { + sb.append("***CRC-FAIL*** "); + } + + sb.append("PDU RESPONSE"); + + if(getHeader().hasSourceLLID()) + { + sb.append(" FROM:").append(getHeader().getSourceLLID()); + } + + sb.append(" TO:").append(getHeader().getTargetLLID()); + sb.append(" ").append(getResponseText()); + return sb.toString(); + } - sb.append(" ").append(getHeader().toString()); + public String getResponseText() + { + StringBuilder sb = new StringBuilder(); + + sb.append(getHeader().getResponse()); if(getPDUSequence().isComplete()) { @@ -60,7 +91,6 @@ public String toString() if(getPDUSequence().getDataBlocks().size() > 0) { sb.append(" BLOCKS TO FOLLOW:").append(getPDUSequence().getDataBlocks().size()); - sb.append(" DATA BLOCKS:").append(getPDUSequence().getDataBlocks().size()); if(!getPDUSequence().getDataBlocks().isEmpty()) @@ -78,10 +108,9 @@ public String toString() else { sb.append(" *INCOMPLETE - RECEIVED ").append(getPDUSequence().getDataBlocks().size()).append("/") - .append(getHeader().getBlocksToFollowCount()).append(" DATA BLOCKS"); + .append(getHeader().getBlocksToFollowCount()).append(" DATA BLOCKS"); } - return sb.toString(); } @@ -153,4 +182,21 @@ private List getMissingBlockNumbers(DataBlock dataBlock, int offset) return missingBlockNumbers; } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getHeader().getTargetLLID()); + + if(getHeader().hasSourceLLID()) + { + mIdentifiers.add(getHeader().getSourceLLID()); + } + } + + return mIdentifiers; + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/umbtc/UMBTCMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/umbtc/UMBTCMessage.java index e29ff61da..ed186cd8b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/umbtc/UMBTCMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/umbtc/UMBTCMessage.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.umbtc; @@ -24,15 +23,14 @@ import io.github.dsheirer.message.IBitErrorProvider; import io.github.dsheirer.module.decode.p25.P25Utils; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.DataBlock; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.UnconfirmedDataBlock; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; - import java.util.List; -public abstract class UMBTCMessage extends P25Message implements IBitErrorProvider +public abstract class UMBTCMessage extends P25P1Message implements IBitErrorProvider { protected static final int[] HEADER_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/umbtc/isp/UMBTCTelephoneInterconnectRequestExplicitDialing.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/umbtc/isp/UMBTCTelephoneInterconnectRequestExplicitDialing.java index d89624388..06c6d1f51 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/umbtc/isp/UMBTCTelephoneInterconnectRequestExplicitDialing.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/umbtc/isp/UMBTCTelephoneInterconnectRequestExplicitDialing.java @@ -24,6 +24,7 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.IBitErrorProvider; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.telephone.APCO25TelephoneNumber; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; @@ -34,7 +35,7 @@ import java.util.ArrayList; import java.util.List; -public class UMBTCTelephoneInterconnectRequestExplicitDialing extends UMBTCMessage implements IBitErrorProvider +public class UMBTCTelephoneInterconnectRequestExplicitDialing extends UMBTCMessage implements IBitErrorProvider, IServiceOptionsProvider { private static final int[] BLOCK_0_DIGIT_COUNT = {8, 9, 10, 11, 12, 13, 14, 15}; private static final int[] BLOCK_0_SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; @@ -58,15 +59,15 @@ public String toString() { sb.append(" TO:").append(getTelephoneNumber()); } - if(getVoiceServiceOptions() != null) + if(getServiceOptions() != null) { - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); } return sb.toString(); } - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null && hasDataBlock(0)) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java index 0645fe9ac..84de06257 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,11 +14,10 @@ * * 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.tdu; -import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.edac.Golay24; @@ -27,14 +25,13 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWordFactory; - import java.util.Collections; import java.util.List; -public class TDULinkControlMessage extends P25Message implements IFrequencyBandReceiver +public class TDULinkControlMessage extends P25P1Message implements IFrequencyBandReceiver { public static final int[] LC_HEX_0 = {0, 1, 2, 3, 4, 5}; public static final int[] LC_HEX_1 = {6, 7, 8, 9, 10, 11}; @@ -149,7 +146,7 @@ private void createLinkControlWord() boolean irrecoverableErrors = REED_SOLOMON_24_12_13_P25.decode(input, output); //Transfer error corrected output to a new binary message - BinaryMessage binaryMessage = new BinaryMessage(72); + CorrectedBinaryMessage binaryMessage = new CorrectedBinaryMessage(72); int pointer = 0; @@ -164,11 +161,7 @@ private void createLinkControlWord() } mLinkControlWord = LinkControlWordFactory.create(binaryMessage); - - if(irrecoverableErrors) - { - mLinkControlWord.setValid(false); - } + mLinkControlWord.setValid(!irrecoverableErrors); //If we corrected any bit errors, update the original message with the bit error count for(int x = 0; x < 23; x++) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDUMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDUMessage.java index 797abdbb2..4f2d29c7d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDUMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDUMessage.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.tdu; @@ -23,12 +22,11 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; - +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import java.util.Collections; import java.util.List; -public class TDUMessage extends P25Message +public class TDUMessage extends P25P1Message { public TDUMessage(CorrectedBinaryMessage message, int nac, long timestamp) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java index 4b742fc4a..b8335e640 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -58,7 +58,7 @@ public enum Opcode OSP_RESERVED_1B(27, "RESERVED_1B", "OSP RESERVED 27"), OSP_MESSAGE_UPDATE(28, "MESSAGE_UPDATE", "MESSAGE UPDATE"), OSP_RADIO_UNIT_MONITOR_COMMAND(29, "RADIO_MONITR_CMD", "RADIO MONITOR COMMAND"), - OSP_RESERVED_1E(30, "RESERVED_1E", "OSP RESERVED 30"), + OSP_RADIO_UNIT_MONITOR_ENHANCED_COMMAND(30, "RADIO UNIT MONITOR ENHANCED COMMAND", "RADIO UNIT MONITOR ENHANCED COMMAND"), OSP_CALL_ALERT(31, "CALL_ALERT", "CALL ALERT"), OSP_ACKNOWLEDGE_RESPONSE(32, "ACK_RESPONSE_FNE", "ACKNOWLEDGE RESPONSE"), OSP_QUEUED_RESPONSE(33, "QUEUED_RESPONSE", "QUEUED RESPONSE"), @@ -90,8 +90,8 @@ public enum Opcode OSP_NETWORK_STATUS_BROADCAST(59, "NET_STATUS_BCAST", "NETWORK STATUS BROADCAST"), OSP_ADJACENT_STATUS_BROADCAST(60, "ADJ SITE STATUS", "ADJACENT SITE STATUS"), OSP_IDENTIFIER_UPDATE(61, "IDEN_UPDATE", "IDENTIFIER UPDATE"), - OSP_PROTECTION_PARAMETER_BROADCAST(62, "ENCRYPT_PAR_BCST", "ENCRYPTION PARAMENTERS BROADCAST"), - OSP_PROTECTION_PARAMETER_UPDATE(63, "ENCRYPT_PAR_UPDT", "ENCRYPTION PARAMETERS UPDATE"), + OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN(62, "ADJ_STS_BCST_UNC", "ADJACENT STATUS BROADCAST UNCOORDINATED BAND PLAN"), + OSP_RESERVED_3F(63, "OSP RESERVED 3F", "OSP RESERVED 63"), OSP_UNKNOWN(-1, "OSP UNKNOWN", "OSP UNKNOWN OPCODE"), //Vendor: standard, Inbound Service Packet (ISP) @@ -162,18 +162,26 @@ public enum Opcode ISP_UNKNOWN(-1, "ISP UNKNOWN", "ISP UNKNOWN OPCODE"), //Vendor: motorola, Inbound Service Packet (ISP) + MOTOROLA_ISP_GROUP_REGROUP_VOICE_REQUEST(0, "MOTOROLA GROUP REGROUP VOICE REQUEST", "MOTOROLA GROUP REGROUP VOICE REQUEST"), + MOTOROLA_ISP_EXTENDED_FUNCTION_RESPONSE(1, "MOTOROLA EXTENDED FUNCTION RESPONSE", "MOTOROLA EXTENDED FUNCTION RESPONSE"), MOTOROLA_ISP_UNKNOWN(-1, "MOTOROLA ISP UNKNOWN OPCODE", "MOTOROLA ISP UNKNOWN OPCODE"), //Vendor: motorola, Outbound Service Packet (OSP) - MOTOROLA_OSP_PATCH_GROUP_ADD(0, "PATCH GROUP ADD", "MOTOROLA PATCH GROUP ADD"), - MOTOROLA_OSP_PATCH_GROUP_DELETE(1, "PATCH GROUP DELE", "MOTOROLA PATCH GROUP DELETE"), - MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT(2, "PTCH GRP VCHN GR", "MOTOROLA PATCH GROUP CHANNEL GRANT"), - MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE(3, "PTCH GRP VCH UPD", "MOTOROLA PATCH GROUP CHANNEL GRANT UPDATE"), - MOTOROLA_OSP_TRAFFIC_CHANNEL_ID(5, "TRAFFIC CHANNEL", "MOTOROLA TRAFFIC CHANNEL"), - MOTOROLA_OSP_DENY_RESPONSE(7, "DENY RESPONSE", "MOTOROLA DENY RESPONSE"), - MOTOROLA_OSP_SYSTEM_LOADING(9, "SYSTEM LOADING", "SYSTEM LOADING"), + MOTOROLA_OSP_GROUP_REGROUP_ADD(0, "MOTOROLA GROUP REGROUP ADD", "MOTOROLA GROUP REGROUP ADD"), + MOTOROLA_OSP_GROUP_REGROUP_DELETE(1, "MOTOROLA GROUP REGROUP DELETE", "MOTOROLA GROUP REGROUP DELETE"), + MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT(2, "MOTOROLA GROUP REGROUP CHANNEL GRANT", "MOTOROLA GROUP REGROUP CHANNEL GRANT"), + MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE(3, "PTCH GRP VCH UPD", "MOTOROLA PATCH GROUP CHANNEL GRANT UPDATE"), + MOTOROLA_OSP_EXTENDED_FUNCTION_COMMAND(4, "MOTOROLA EXTENDED FUNCTION COMMAND", "MOTOROLA EXTENDED FUNCTION COMMAND"), + MOTOROLA_OSP_TRAFFIC_CHANNEL_ID(5, "MOTOROLA TRAFFIC CHANNEL", "MOTOROLA TRAFFIC CHANNEL"), + MOTOROLA_OSP_QUEUED_RESPONSE(6, "MOTOROLA QUEUED RESPONSE", "MOTOROLA QUEUED RESPONSE"), + MOTOROLA_OSP_DENY_RESPONSE(7, "MOTOROLA DENY RESPONSE", "MOTOROLA DENY RESPONSE"), + MOTOROLA_OSP_ACKNOWLEDGE_RESPONSE(8, "MOTOROLA ACKNOWLEDGE RESPONSE", "MOTOROLA ACKNOWLEDGE RESPONSE"), + MOTOROLA_OSP_SYSTEM_LOADING(9, "MOTOROLA SYSTEM LOADING", "MOTOROLA SYSTEM LOADING"), + MOTOROLA_OSP_EMERGENCY_ALARM_ACTIVATION(10, "MOTOROLA EMERGENCY ALARM ACTIVATION", "MOTOROLA EMERGENCY ALARM ACTIVATION"), MOTOROLA_OSP_BASE_STATION_ID(11, "CCH BASE STAT ID", "CONTROL CHANNEL BASE STATION ID"), MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN(14, "CCH PLND SHUTDWN", "CONTROL CHANNEL PLANNED SHUTDOWN"), + MOTOROLA_OSP_OPCODE_15(15, "MOTOROLA OPCODE 15", "MOTOROLA OPCODE 15"), + //Opcode 22 - observed on PA-STARNET VHF Phase 1 CC site: 1690423FFFFFFFFF0000D458 & 9690423FFFFFFFFF0000306C MOTOROLA_OSP_UNKNOWN(-1, "MOTOROLA OSP UNKNOWN OPCODE", "MOTOROLA OSP UNKNOWN OPCODE"), //Vendor: L3Harris, Inbound Service Packet (ISP) @@ -210,7 +218,7 @@ public enum Opcode * OSP standard opcodes */ public static final EnumSet STANDARD_OUTBOUND_OPCODES = EnumSet.range(OSP_GROUP_VOICE_CHANNEL_GRANT, - OSP_PROTECTION_PARAMETER_UPDATE); + OSP_RESERVED_3F); /** * ISP standard opcodes @@ -259,8 +267,8 @@ public enum Opcode OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT, OSP_TDMA_SYNC_BROADCAST, OSP_IDENTIFIER_UPDATE_TDMA, OSP_IDENTIFIER_UPDATE_VHF_UHF_BANDS, OSP_TIME_DATE_ANNOUNCEMENT, OSP_SYSTEM_SERVICE_BROADCAST, OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST, OSP_RFSS_STATUS_BROADCAST, OSP_NETWORK_STATUS_BROADCAST, - OSP_ADJACENT_STATUS_BROADCAST, OSP_IDENTIFIER_UPDATE, OSP_PROTECTION_PARAMETER_BROADCAST, - OSP_PROTECTION_PARAMETER_UPDATE); + OSP_ADJACENT_STATUS_BROADCAST, OSP_IDENTIFIER_UPDATE, OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN, + OSP_RESERVED_3F); /** * OSP network command, request and response @@ -285,9 +293,11 @@ public enum Opcode /** * Motorola opcodes */ - public static final EnumSet MOTOROLA = EnumSet.of(MOTOROLA_ISP_UNKNOWN, MOTOROLA_OSP_PATCH_GROUP_ADD, - MOTOROLA_OSP_PATCH_GROUP_DELETE, MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT, - MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE, MOTOROLA_OSP_TRAFFIC_CHANNEL_ID, + public static final EnumSet MOTOROLA = EnumSet.of(MOTOROLA_ISP_UNKNOWN, + MOTOROLA_ISP_GROUP_REGROUP_VOICE_REQUEST, MOTOROLA_ISP_EXTENDED_FUNCTION_RESPONSE, + MOTOROLA_OSP_GROUP_REGROUP_ADD, + MOTOROLA_OSP_GROUP_REGROUP_DELETE, MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT, + MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE, MOTOROLA_OSP_TRAFFIC_CHANNEL_ID, MOTOROLA_OSP_DENY_RESPONSE, MOTOROLA_OSP_SYSTEM_LOADING, MOTOROLA_OSP_BASE_STATION_ID, MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN, MOTOROLA_OSP_UNKNOWN); @@ -433,30 +443,42 @@ public static Opcode fromValue(int value, Direction direction, Vendor vendor) case MOTOROLA: if(direction == Direction.INBOUND) { - return MOTOROLA_ISP_UNKNOWN; + switch(value) + { + case 0x00: + return MOTOROLA_ISP_GROUP_REGROUP_VOICE_REQUEST; + case 0x01: + return MOTOROLA_ISP_EXTENDED_FUNCTION_RESPONSE; + default: + return MOTOROLA_ISP_UNKNOWN; + } } else { switch(value) { case 0x00: - return MOTOROLA_OSP_PATCH_GROUP_ADD; + return MOTOROLA_OSP_GROUP_REGROUP_ADD; case 0x01: - return MOTOROLA_OSP_PATCH_GROUP_DELETE; + return MOTOROLA_OSP_GROUP_REGROUP_DELETE; case 0x02: - return MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT; + return MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT; case 0x03: - return MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE; + return MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE; case 0x05: return MOTOROLA_OSP_TRAFFIC_CHANNEL_ID; case 0x07: return MOTOROLA_OSP_DENY_RESPONSE; case 0x09: return MOTOROLA_OSP_SYSTEM_LOADING; + case 0x0A: + return MOTOROLA_OSP_EMERGENCY_ALARM_ACTIVATION; case 0x0B: return MOTOROLA_OSP_BASE_STATION_ID; case 0x0E: return MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN; + case 0x0F: + return MOTOROLA_OSP_OPCODE_15; default: return MOTOROLA_OSP_UNKNOWN; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessage.java index 69c34bebe..eb071d1d6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessage.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.tsbk; @@ -26,21 +25,21 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.P25Utils; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.reference.Direction; import io.github.dsheirer.module.decode.p25.reference.Vendor; - import java.util.List; /** * APCO 25 Trunking Signalling Block (TSBK) */ -public abstract class TSBKMessage extends P25Message +public abstract class TSBKMessage extends P25P1Message { private static final int LAST_BLOCK_FLAG = 0; private static final int ENCRYPTION_FLAG = 1; private static final int[] OPCODE = {2, 3, 4, 5, 6, 7}; private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15}; + //16-bit CRC starts at OCTET 10, Bits 80-95 private P25P1DataUnitID mDataUnitID; @@ -56,14 +55,6 @@ public TSBKMessage(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, i { super(message, nac, timestamp); mDataUnitID = dataUnitID; - - //The CRC-CCITT can correct up to 1 bit error or detect 2 or more errors. We mark the message as - //invalid if the algorithm detects more than 1 correctable error. - int errors = CRCP25.correctCCITT80(message, 0, 80); - if(errors > 1) - { - setValid(false); - } } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessageFactory.java index c63686007..03f3f0528 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessageFactory.java @@ -1,43 +1,48 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.tsbk; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRCP25; import io.github.dsheirer.edac.trellis.ViterbiDecoder_1_2_P25; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.P25P1Interleave; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.isp.UnknownHarrisISPMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.HarrisGroupRegroupExplicitEncryptionCommand; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.L3HarrisGroupRegroupExplicitEncryptionCommand; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.UnknownHarrisOSPMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.isp.MotorolaExtendedFunctionResponse; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.isp.MotorolaGroupRegroupVoiceRequest; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.isp.UnknownMotorolaISPMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.ChannelLoading; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaAcknowledgeResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaBaseStationId; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaDenyResponse; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaEmergencyAlarmActivation; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExtendedFunctionCommand; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupAddCommand; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelGrant; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelUpdate; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupDeleteCommand; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaOpcode15; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaQueuedResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaTrafficChannel; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupAdd; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupDelete; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrant; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrantUpdate; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PlannedChannelShutdown; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.UnknownMotorolaOSPMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.isp.AuthenticationQuery; @@ -103,7 +108,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SecondaryControlChannelBroadcastExplicit; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.StatusQuery; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.StatusUpdate; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SyncBroadcast; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SynchronizationBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SystemServiceBroadcast; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.TelephoneInterconnectAnswerRequest; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.TelephoneInterconnectVoiceChannelGrant; @@ -144,203 +149,328 @@ public static TSBKMessage create(Direction direction, P25P1DataUnitID dataUnitID return null; } + //The CRC-CCITT can correct up to 1 bit error or detect 2 or more errors. We mark the message as + //invalid if the algorithm detects more than 1 correctable error. + int errors = CRCP25.correctCCITT80(message, 0, 80); + Vendor vendor = TSBKMessage.getVendor(message); Opcode opcode = TSBKMessage.getOpcode(message, direction, vendor); + TSBKMessage tsbk = null; + switch(opcode) { case ISP_AUTHENTICATION_QUERY_OBSOLETE: - return new AuthenticationQuery(dataUnitID, message, nac, timestamp); + tsbk = new AuthenticationQuery(dataUnitID, message, nac, timestamp); + break; case ISP_CALL_ALERT_REQUEST: - return new CallAlertRequest(dataUnitID, message, nac, timestamp); + tsbk = new CallAlertRequest(dataUnitID, message, nac, timestamp); + break; case ISP_CANCEL_SERVICE_REQUEST: - return new CancelServiceRequest(dataUnitID, message, nac, timestamp); + tsbk = new CancelServiceRequest(dataUnitID, message, nac, timestamp); + break; case ISP_EMERGENCY_ALARM_REQUEST: - return new EmergencyAlarmRequest(dataUnitID, message, nac, timestamp); + tsbk = new EmergencyAlarmRequest(dataUnitID, message, nac, timestamp); + break; case ISP_EXTENDED_FUNCTION_RESPONSE: - return new ExtendedFunctionResponse(dataUnitID, message, nac, timestamp); + tsbk = new ExtendedFunctionResponse(dataUnitID, message, nac, timestamp); + break; case ISP_GROUP_AFFILIATION_QUERY_RESPONSE: - return new GroupAffiliationQueryResponse(dataUnitID, message, nac, timestamp); + tsbk = new GroupAffiliationQueryResponse(dataUnitID, message, nac, timestamp); + break; case ISP_GROUP_AFFILIATION_REQUEST: - return new GroupAffiliationRequest(dataUnitID, message, nac, timestamp); + tsbk = new GroupAffiliationRequest(dataUnitID, message, nac, timestamp); + break; case ISP_GROUP_VOICE_SERVICE_REQUEST: - return new GroupVoiceServiceRequest(dataUnitID, message, nac, timestamp); + tsbk = new GroupVoiceServiceRequest(dataUnitID, message, nac, timestamp); + break; case ISP_IDENTIFIER_UPDATE_REQUEST: - return new FrequencyBandUpdateRequest(dataUnitID, message, nac, timestamp); + tsbk = new FrequencyBandUpdateRequest(dataUnitID, message, nac, timestamp); + break; case ISP_INDIVIDUAL_DATA_SERVICE_REQUEST: - return new IndividualDataServiceRequest(dataUnitID, message, nac, timestamp); + tsbk = new IndividualDataServiceRequest(dataUnitID, message, nac, timestamp); + break; case ISP_LOCATION_REGISTRATION_REQUEST: - return new LocationRegistrationRequest(dataUnitID, message, nac, timestamp); + tsbk = new LocationRegistrationRequest(dataUnitID, message, nac, timestamp); + break; case ISP_MESSAGE_UPDATE_REQUEST: - return new MessageUpdateRequest(dataUnitID, message, nac, timestamp); + tsbk = new MessageUpdateRequest(dataUnitID, message, nac, timestamp); + break; case ISP_PROTECTION_PARAMETER_REQUEST: - return new ProtectionParameterRequest(dataUnitID, message, nac, timestamp); + tsbk = new ProtectionParameterRequest(dataUnitID, message, nac, timestamp); + break; case ISP_RADIO_UNIT_MONITOR_REQUEST: - return new RadioUnitMonitorRequest(dataUnitID, message, nac, timestamp); + tsbk = new RadioUnitMonitorRequest(dataUnitID, message, nac, timestamp); + break; case ISP_ROAMING_ADDRESS_REQUEST: - return new RoamingAddressRequest(dataUnitID, message, nac, timestamp); + tsbk = new RoamingAddressRequest(dataUnitID, message, nac, timestamp); + break; case ISP_ROAMING_ADDRESS_RESPONSE: - return new RoamingAddressResponse(dataUnitID, message, nac, timestamp); + tsbk = new RoamingAddressResponse(dataUnitID, message, nac, timestamp); + break; case ISP_SNDCP_DATA_CHANNEL_REQUEST: - return new SNDCPDataChannelRequest(dataUnitID, message, nac, timestamp); + tsbk = new SNDCPDataChannelRequest(dataUnitID, message, nac, timestamp); + break; case ISP_SNDCP_DATA_PAGE_RESPONSE: - return new SNDCPDataPageResponse(dataUnitID, message, nac, timestamp); + tsbk = new SNDCPDataPageResponse(dataUnitID, message, nac, timestamp); + break; case ISP_SNDCP_RECONNECT_REQUEST: - return new SNDCPReconnectRequest(dataUnitID, message, nac, timestamp); + tsbk = new SNDCPReconnectRequest(dataUnitID, message, nac, timestamp); + break; case ISP_STATUS_QUERY_REQUEST: - return new StatusQueryRequest(dataUnitID, message, nac, timestamp); + tsbk = new StatusQueryRequest(dataUnitID, message, nac, timestamp); + break; case ISP_STATUS_QUERY_RESPONSE: - return new StatusQueryResponse(dataUnitID, message, nac, timestamp); + tsbk = new StatusQueryResponse(dataUnitID, message, nac, timestamp); + break; case ISP_STATUS_UPDATE_REQUEST: - return new StatusUpdateRequest(dataUnitID, message, nac, timestamp); + tsbk = new StatusUpdateRequest(dataUnitID, message, nac, timestamp); + break; case ISP_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE: - return new TelephoneInterconnectAnswerResponse(dataUnitID, message, nac, timestamp); + tsbk = new TelephoneInterconnectAnswerResponse(dataUnitID, message, nac, timestamp); + break; case ISP_TELEPHONE_INTERCONNECT_PSTN_REQUEST: - return new TelephoneInterconnectPstnRequest(dataUnitID, message, nac, timestamp); + tsbk = new TelephoneInterconnectPstnRequest(dataUnitID, message, nac, timestamp); + break; case ISP_UNIT_ACKNOWLEDGE_RESPONSE: - return new UnitAcknowledgeResponse(dataUnitID, message, nac, timestamp); + tsbk = new UnitAcknowledgeResponse(dataUnitID, message, nac, timestamp); + break; case ISP_UNIT_REGISTRATION_REQUEST: - return new UnitRegistrationRequest(dataUnitID, message, nac, timestamp); + tsbk = new UnitRegistrationRequest(dataUnitID, message, nac, timestamp); + break; case ISP_UNIT_DE_REGISTRATION_REQUEST: - return new UnitDeRegistrationRequest(dataUnitID, message, nac, timestamp); + tsbk = new UnitDeRegistrationRequest(dataUnitID, message, nac, timestamp); + break; case ISP_UNIT_TO_UNIT_ANSWER_RESPONSE: - return new UnitToUnitVoiceServiceAnswerResponse(dataUnitID, message, nac, timestamp); + tsbk = new UnitToUnitVoiceServiceAnswerResponse(dataUnitID, message, nac, timestamp); + break; case ISP_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST: - return new UnitToUnitVoiceServiceRequest(dataUnitID, message, nac, timestamp); + tsbk = new UnitToUnitVoiceServiceRequest(dataUnitID, message, nac, timestamp); + break; case OSP_ACKNOWLEDGE_RESPONSE: - return new AcknowledgeResponse(dataUnitID, message, nac, timestamp); + tsbk = new AcknowledgeResponse(dataUnitID, message, nac, timestamp); + break; case OSP_ADJACENT_STATUS_BROADCAST: - return new AdjacentStatusBroadcast(dataUnitID, message, nac, timestamp); + tsbk = new AdjacentStatusBroadcast(dataUnitID, message, nac, timestamp); + break; case OSP_AUTHENTICATION_COMMAND: - return new AuthenticationCommand(dataUnitID, message, nac, timestamp); + tsbk = new AuthenticationCommand(dataUnitID, message, nac, timestamp); + break; case OSP_CALL_ALERT: - return new CallAlert(dataUnitID, message, nac, timestamp); + tsbk = new CallAlert(dataUnitID, message, nac, timestamp); + break; case OSP_DENY_RESPONSE: - return new DenyResponse(dataUnitID, message, nac, timestamp); + tsbk = new DenyResponse(dataUnitID, message, nac, timestamp); + break; case OSP_EXTENDED_FUNCTION_COMMAND: - return new ExtendedFunctionCommand(dataUnitID, message, nac, timestamp); + tsbk = new ExtendedFunctionCommand(dataUnitID, message, nac, timestamp); + break; case OSP_GROUP_AFFILIATION_QUERY: - return new GroupAffiliationQuery(dataUnitID, message, nac, timestamp); + tsbk = new GroupAffiliationQuery(dataUnitID, message, nac, timestamp); + break; case OSP_GROUP_AFFILIATION_RESPONSE: - return new GroupAffiliationResponse(dataUnitID, message, nac, timestamp); + tsbk = new GroupAffiliationResponse(dataUnitID, message, nac, timestamp); + break; case OSP_GROUP_DATA_CHANNEL_ANNOUNCEMENT: - return new GroupDataChannelAnnouncement(dataUnitID, message, nac, timestamp); + tsbk = new GroupDataChannelAnnouncement(dataUnitID, message, nac, timestamp); + break; case OSP_GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: - return new GroupDataChannelAnnouncementExplicit(dataUnitID, message, nac, timestamp); + tsbk = new GroupDataChannelAnnouncementExplicit(dataUnitID, message, nac, timestamp); + break; case OSP_GROUP_DATA_CHANNEL_GRANT: - return new GroupDataChannelGrant(dataUnitID, message, nac, timestamp); + tsbk = new GroupDataChannelGrant(dataUnitID, message, nac, timestamp); + break; case OSP_GROUP_VOICE_CHANNEL_GRANT: - return new GroupVoiceChannelGrant(dataUnitID, message, nac, timestamp); + tsbk = new GroupVoiceChannelGrant(dataUnitID, message, nac, timestamp); + break; case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE: - return new GroupVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + tsbk = new GroupVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + break; case OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: - return new GroupVoiceChannelGrantUpdateExplicit(dataUnitID, message, nac, timestamp); + tsbk = new GroupVoiceChannelGrantUpdateExplicit(dataUnitID, message, nac, timestamp); + break; case OSP_IDENTIFIER_UPDATE: - return new FrequencyBandUpdate(dataUnitID, message, nac, timestamp); + tsbk = new FrequencyBandUpdate(dataUnitID, message, nac, timestamp); + break; case OSP_IDENTIFIER_UPDATE_TDMA: - return new FrequencyBandUpdateTDMA(dataUnitID, message, nac, timestamp); + tsbk = new FrequencyBandUpdateTDMA(dataUnitID, message, nac, timestamp); + break; case OSP_IDENTIFIER_UPDATE_VHF_UHF_BANDS: - return new FrequencyBandUpdateVUHF(dataUnitID, message, nac, timestamp); + tsbk = new FrequencyBandUpdateVUHF(dataUnitID, message, nac, timestamp); + break; case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: - return new IndividualDataChannelGrant(dataUnitID, message, nac, timestamp); + tsbk = new IndividualDataChannelGrant(dataUnitID, message, nac, timestamp); + break; case OSP_LOCATION_REGISTRATION_RESPONSE: - return new LocationRegistrationResponse(dataUnitID, message, nac, timestamp); + tsbk = new LocationRegistrationResponse(dataUnitID, message, nac, timestamp); + break; case OSP_MESSAGE_UPDATE: - return new MessageUpdate(dataUnitID, message, nac, timestamp); + tsbk = new MessageUpdate(dataUnitID, message, nac, timestamp); + break; case OSP_NETWORK_STATUS_BROADCAST: - return new NetworkStatusBroadcast(dataUnitID, message, nac, timestamp); - case OSP_PROTECTION_PARAMETER_UPDATE: - return new ProtectionParameterUpdate(dataUnitID, message, nac, timestamp); + tsbk = new NetworkStatusBroadcast(dataUnitID, message, nac, timestamp); + break; + case OSP_RESERVED_3F: + tsbk = new ProtectionParameterUpdate(dataUnitID, message, nac, timestamp); + break; case OSP_RADIO_UNIT_MONITOR_COMMAND: - return new RadioUnitMonitorCommand(dataUnitID, message, nac, timestamp); + tsbk = new RadioUnitMonitorCommand(dataUnitID, message, nac, timestamp); + break; case OSP_QUEUED_RESPONSE: - return new QueuedResponse(dataUnitID, message, nac, timestamp); + tsbk = new QueuedResponse(dataUnitID, message, nac, timestamp); + break; case OSP_ROAMING_ADDRESS_COMMAND: - return new RoamingAddressCommand(dataUnitID, message, nac, timestamp); + tsbk = new RoamingAddressCommand(dataUnitID, message, nac, timestamp); + break; case OSP_RFSS_STATUS_BROADCAST: - return new RFSSStatusBroadcast(dataUnitID, message, nac, timestamp); + tsbk = new RFSSStatusBroadcast(dataUnitID, message, nac, timestamp); + break; case OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST: - return new SecondaryControlChannelBroadcast(dataUnitID, message, nac, timestamp); + tsbk = new SecondaryControlChannelBroadcast(dataUnitID, message, nac, timestamp); + break; case OSP_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - return new SecondaryControlChannelBroadcastExplicit(dataUnitID, message, nac, timestamp); + tsbk = new SecondaryControlChannelBroadcastExplicit(dataUnitID, message, nac, timestamp); + break; case OSP_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: - return new SNDCPDataChannelAnnouncementExplicit(dataUnitID, message, nac, timestamp); + tsbk = new SNDCPDataChannelAnnouncementExplicit(dataUnitID, message, nac, timestamp); + break; case OSP_SNDCP_DATA_CHANNEL_GRANT: - return new SNDCPDataChannelGrant(dataUnitID, message, nac, timestamp); + tsbk = new SNDCPDataChannelGrant(dataUnitID, message, nac, timestamp); + break; case OSP_SNDCP_DATA_PAGE_REQUEST: - return new SNDCPDataPageRequest(dataUnitID, message, nac, timestamp); + tsbk = new SNDCPDataPageRequest(dataUnitID, message, nac, timestamp); + break; case OSP_STATUS_QUERY: - return new StatusQuery(dataUnitID, message, nac, timestamp); + tsbk = new StatusQuery(dataUnitID, message, nac, timestamp); + break; case OSP_STATUS_UPDATE: - return new StatusUpdate(dataUnitID, message, nac, timestamp); + tsbk = new StatusUpdate(dataUnitID, message, nac, timestamp); + break; case OSP_TDMA_SYNC_BROADCAST: - return new SyncBroadcast(dataUnitID, message, nac, timestamp); + tsbk = new SynchronizationBroadcast(dataUnitID, message, nac, timestamp); + break; case OSP_SYSTEM_SERVICE_BROADCAST: - return new SystemServiceBroadcast(dataUnitID, message, nac, timestamp); + tsbk = new SystemServiceBroadcast(dataUnitID, message, nac, timestamp); + break; case OSP_TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - return new TelephoneInterconnectAnswerRequest(dataUnitID, message, nac, timestamp); + tsbk = new TelephoneInterconnectAnswerRequest(dataUnitID, message, nac, timestamp); + break; case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - return new TelephoneInterconnectVoiceChannelGrant(dataUnitID, message, nac, timestamp); + tsbk = new TelephoneInterconnectVoiceChannelGrant(dataUnitID, message, nac, timestamp); + break; case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: - return new TelephoneInterconnectVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + tsbk = new TelephoneInterconnectVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + break; case OSP_UNIT_DEREGISTRATION_ACKNOWLEDGE: - return new UnitDeRegistrationAcknowledge(dataUnitID, message, nac, timestamp); + tsbk = new UnitDeRegistrationAcknowledge(dataUnitID, message, nac, timestamp); + break; case OSP_UNIT_REGISTRATION_COMMAND: - return new UnitRegistrationCommand(dataUnitID, message, nac, timestamp); + tsbk = new UnitRegistrationCommand(dataUnitID, message, nac, timestamp); + break; case OSP_UNIT_REGISTRATION_RESPONSE: - return new UnitRegistrationResponse(dataUnitID, message, nac, timestamp); + tsbk = new UnitRegistrationResponse(dataUnitID, message, nac, timestamp); + break; case OSP_UNIT_TO_UNIT_ANSWER_REQUEST: - return new UnitToUnitAnswerRequest(dataUnitID, message, nac, timestamp); + tsbk = new UnitToUnitAnswerRequest(dataUnitID, message, nac, timestamp); + break; case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - return new UnitToUnitVoiceChannelGrant(dataUnitID, message, nac, timestamp); + tsbk = new UnitToUnitVoiceChannelGrant(dataUnitID, message, nac, timestamp); + break; case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: - return new UnitToUnitVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + tsbk = new UnitToUnitVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + break; case HARRIS_ISP_UNKNOWN: - return new UnknownHarrisISPMessage(dataUnitID, message, nac, timestamp); + tsbk = new UnknownHarrisISPMessage(dataUnitID, message, nac, timestamp); + break; case HARRIS_OSP_GRG_EXENC_CMD: - return new HarrisGroupRegroupExplicitEncryptionCommand(dataUnitID, message, nac, timestamp); + tsbk = new L3HarrisGroupRegroupExplicitEncryptionCommand(dataUnitID, message, nac, timestamp); + break; case HARRIS_OSP_UNKNOWN: - return new UnknownHarrisOSPMessage(dataUnitID, message, nac, timestamp); + tsbk = new UnknownHarrisOSPMessage(dataUnitID, message, nac, timestamp); + break; case MOTOROLA_ISP_UNKNOWN: - return new UnknownMotorolaISPMessage(dataUnitID, message, nac, timestamp); + tsbk = new UnknownMotorolaISPMessage(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_ISP_EXTENDED_FUNCTION_RESPONSE: + tsbk = new MotorolaExtendedFunctionResponse(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_ISP_GROUP_REGROUP_VOICE_REQUEST: + tsbk = new MotorolaGroupRegroupVoiceRequest(dataUnitID, message, nac, timestamp); + break; case MOTOROLA_OSP_BASE_STATION_ID: - return new MotorolaBaseStationId(dataUnitID, message, nac, timestamp); + tsbk = new MotorolaBaseStationId(dataUnitID, message, nac, timestamp); + break; case MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN: - return new PlannedChannelShutdown(dataUnitID, message, nac, timestamp); + tsbk = new PlannedChannelShutdown(dataUnitID, message, nac, timestamp); + break; case MOTOROLA_OSP_DENY_RESPONSE: - return new MotorolaDenyResponse(dataUnitID, message, nac, timestamp); + tsbk = new MotorolaDenyResponse(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_OSP_QUEUED_RESPONSE: + tsbk = new MotorolaQueuedResponse(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_OSP_ACKNOWLEDGE_RESPONSE: + tsbk = new MotorolaAcknowledgeResponse(dataUnitID, message, nac, timestamp); + break; case MOTOROLA_OSP_TRAFFIC_CHANNEL_ID: - return new MotorolaTrafficChannel(dataUnitID, message, nac, timestamp); - case MOTOROLA_OSP_PATCH_GROUP_ADD: - return new PatchGroupAdd(dataUnitID, message, nac, timestamp); - case MOTOROLA_OSP_PATCH_GROUP_DELETE: - return new PatchGroupDelete(dataUnitID, message, nac, timestamp); - case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT: - return new PatchGroupVoiceChannelGrant(dataUnitID, message, nac, timestamp); - case MOTOROLA_OSP_PATCH_GROUP_CHANNEL_GRANT_UPDATE: - return new PatchGroupVoiceChannelGrantUpdate(dataUnitID, message, nac, timestamp); + tsbk = new MotorolaTrafficChannel(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_OSP_GROUP_REGROUP_ADD: + tsbk = new MotorolaGroupRegroupAddCommand(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_OSP_GROUP_REGROUP_DELETE: + tsbk = new MotorolaGroupRegroupDeleteCommand(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT: + tsbk = new MotorolaGroupRegroupChannelGrant(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE: + tsbk = new MotorolaGroupRegroupChannelUpdate(dataUnitID, message, nac, timestamp); + break; case MOTOROLA_OSP_SYSTEM_LOADING: - return new ChannelLoading(dataUnitID, message, nac, timestamp); + tsbk = new ChannelLoading(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_OSP_EXTENDED_FUNCTION_COMMAND: + tsbk = new MotorolaExtendedFunctionCommand(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_OSP_EMERGENCY_ALARM_ACTIVATION: + tsbk = new MotorolaEmergencyAlarmActivation(dataUnitID, message, nac, timestamp); + break; + case MOTOROLA_OSP_OPCODE_15: + tsbk = new MotorolaOpcode15(dataUnitID, message, nac, timestamp); + break; case MOTOROLA_OSP_UNKNOWN: - return new UnknownMotorolaOSPMessage(dataUnitID, message, nac, timestamp); + tsbk = new UnknownMotorolaOSPMessage(dataUnitID, message, nac, timestamp); + break; case UNKNOWN_VENDOR_ISP: - return new UnknownVendorISPMessage(dataUnitID, message, nac, timestamp); + tsbk = new UnknownVendorISPMessage(dataUnitID, message, nac, timestamp); + break; case UNKNOWN_VENDOR_OSP: - return new UnknownVendorOSPMessage(dataUnitID, message, nac, timestamp); + tsbk = new UnknownVendorOSPMessage(dataUnitID, message, nac, timestamp); + break; default: if(direction == Direction.INBOUND) { - return new UnknownISPMessage(dataUnitID, message, nac, timestamp); + tsbk = new UnknownISPMessage(dataUnitID, message, nac, timestamp); } else { - return new UnknownOSPMessage(dataUnitID, message, nac, timestamp); + tsbk = new UnknownOSPMessage(dataUnitID, message, nac, timestamp); } + break; } + + if(tsbk != null && errors > 1) + { + tsbk.setValid(false); + } + + return tsbk; } + } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/VendorISPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/VendorISPMessage.java new file mode 100644 index 000000000..33aeee2c7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/VendorISPMessage.java @@ -0,0 +1,65 @@ +/* + * ***************************************************************************** + * 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.tsbk; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +/** + * Vendor custom TSBK Inbound message + */ +public abstract class VendorISPMessage extends ISPMessage +{ + private static final IntField VENDOR = IntField.length8(OCTET_1_BIT_8); + + /** + * Constructs an inbound (ISP) TSBK from the binary message sequence. + * + * @param dataUnitID TSBK1/2/3 + * @param message binary sequence + * @param nac decoded from the NID + * @param timestamp for the message + */ + public VendorISPMessage(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitID, message, nac, timestamp); + } + + /** + * Utility method to identify the vendor for the specified message. + * @param message containing a vendor code. + * @return vendor or UNKNOWN. + */ + public static Vendor getVendor(CorrectedBinaryMessage message) + { + return Vendor.fromValue(message.getInt(VENDOR)); + } + + /** + * Vendor for this message + * @return vendor + */ + public Vendor getVendor() + { + return Vendor.fromValue(getInt(VENDOR)); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/VendorOSPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/VendorOSPMessage.java new file mode 100644 index 000000000..5cb838e3b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/VendorOSPMessage.java @@ -0,0 +1,65 @@ +/* + * ***************************************************************************** + * 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.tsbk; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +/** + * Vendor custom TSBK Outbound message + */ +public abstract class VendorOSPMessage extends OSPMessage +{ + private static final IntField VENDOR = IntField.length8(OCTET_1_BIT_8); + + /** + * Constructs an inbound (ISP) TSBK from the binary message sequence. + * + * @param dataUnitID TSBK1/2/3 + * @param message binary sequence + * @param nac decoded from the NID + * @param timestamp for the message + */ + public VendorOSPMessage(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitID, message, nac, timestamp); + } + + /** + * Utility method to identify the vendor for the specified message. + * @param message containing a vendor code. + * @return vendor or UNKNOWN. + */ + public static Vendor getVendor(CorrectedBinaryMessage message) + { + return Vendor.fromValue(message.getInt(VENDOR)); + } + + /** + * Vendor for this message + * @return vendor + */ + public Vendor getVendor() + { + return Vendor.fromValue(getInt(VENDOR)); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/HarrisGroupRegroupExplicitEncryptionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/L3HarrisGroupRegroupExplicitEncryptionCommand.java similarity index 83% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/HarrisGroupRegroupExplicitEncryptionCommand.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/L3HarrisGroupRegroupExplicitEncryptionCommand.java index 9ec1a2688..328825dca 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/HarrisGroupRegroupExplicitEncryptionCommand.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/L3HarrisGroupRegroupExplicitEncryptionCommand.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -20,6 +20,7 @@ package io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.patch.PatchGroup; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; @@ -27,26 +28,23 @@ import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; import io.github.dsheirer.module.decode.p25.reference.RegroupOptions; import java.util.ArrayList; import java.util.List; /** - * Harris Group Regroup Action and Explicit Encryption Command. + * L3Harris Group Regroup Explicit Encryption Command. *

* This OSP is used to create, remove or update a Supergroup address associated with one member WGID or WUID with * explicit encryption parameters. - * - * See: https://forums.radioreference.com/threads/duke-energy-p25-system.411183/page-28#post-3908078 */ -public class HarrisGroupRegroupExplicitEncryptionCommand extends OSPMessage +public class L3HarrisGroupRegroupExplicitEncryptionCommand extends VendorOSPMessage { - private static final int[] REGROUP_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] SUPERGROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] ENCRYPTION_KEY_ID = new int[]{40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] TARGET_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, - 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final IntField REGROUP_OPTIONS = IntField.length8(OCTET_2_BIT_16); + private static final IntField SUPERGROUP_ADDRESS = IntField.length16(OCTET_3_BIT_24); + private static final IntField ENCRYPTION_KEY_ID = IntField.length16(OCTET_5_BIT_40); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_56); private TalkgroupIdentifier mSuperGroupIdentifier; private APCO25PatchGroup mPatchGroup; @@ -57,7 +55,7 @@ public class HarrisGroupRegroupExplicitEncryptionCommand extends OSPMessage /** * Constructs a TSBK from the binary message sequence. */ - public HarrisGroupRegroupExplicitEncryptionCommand(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + public L3HarrisGroupRegroupExplicitEncryptionCommand(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) { super(dataUnitId, message, nac, timestamp); } @@ -70,7 +68,7 @@ public RegroupOptions getRegroupOptions() { if(mRegroupOptions == null) { - mRegroupOptions = new RegroupOptions(getMessage().getInt(REGROUP_OPTIONS)); + mRegroupOptions = new RegroupOptions(getInt(REGROUP_OPTIONS)); } return mRegroupOptions; @@ -83,7 +81,7 @@ public RegroupOptions getRegroupOptions() */ public int getEncryptionKeyId() { - return getMessage().getInt(ENCRYPTION_KEY_ID); + return getInt(ENCRYPTION_KEY_ID); } /** @@ -130,11 +128,11 @@ public Identifier getTargetAddress() { if(getRegroupOptions().isTalkgroupAddress()) { - mPatchedIdentifier = APCO25Talkgroup.create(getMessage().getInt(TARGET_ADDRESS)); + mPatchedIdentifier = APCO25Talkgroup.create(getInt(TARGET_ADDRESS)); } else { - mPatchedIdentifier = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mPatchedIdentifier = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/MotorolaExtendedFunctionResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/MotorolaExtendedFunctionResponse.java new file mode 100644 index 000000000..b97cfd59b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/MotorolaExtendedFunctionResponse.java @@ -0,0 +1,172 @@ +/* + * ***************************************************************************** + * 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.tsbk.motorola.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorISPMessage; +import java.util.ArrayList; +import java.util.List; + +/** + * Inbound extended function command or acknowledgement from the SU. + */ +public class MotorolaExtendedFunctionResponse extends VendorISPMessage +{ + private static final IntField CLASS = IntField.length8(OCTET_2_BIT_16); + private static final IntField OPERAND = IntField.length8(OCTET_3_BIT_24); + private static final IntField ARGUMENTS = IntField.length24(OCTET_4_BIT_32); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_7_BIT_56); + + private TalkgroupIdentifier mSupergroup; + private RadioIdentifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs an inbound (ISP) TSBK from the binary message sequence. + * + * @param dataUnitID TSBK1/2/3 + * @param message binary sequence + * @param nac decoded from the NID + * @param timestamp for the message + */ + public MotorolaExtendedFunctionResponse(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitID, message, nac, timestamp); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ").append(getDescription()); + return super.toString(); + } + + public String getDescription() + { + if(isAcknowledgeSupergroupCancel()) + { + return "ACKNOWLEDGE CANCEL SUPERGROUP:" + getSuperGroup(); + } + else if(isAcknowledgeSupergroupCreate()) + { + return "ACKNOWLEDGE CREATE SUPERGROUP:" + getSuperGroup(); + } + else + { + return "UNRECOGNIZED EXTENDED FUNCTION CLASS:" + getFunctionClass() + " OPERAND:" + getFunctionOperand() + + " ARGUMENTS:" + getFunctionArguments(); + } + } + + /** + * Indicates if this is an acknowledge supergroup create command. + */ + public boolean isAcknowledgeSupergroupCreate() + { + return getFunctionClass() == 0x02 && getFunctionOperand() == 0x80; + } + + /** + * Indicates if this is an acknowledge supergroup cancel command. + */ + public boolean isAcknowledgeSupergroupCancel() + { + return getFunctionClass() == 0x02 && getFunctionOperand() == 0x81; + } + + /** + * + * @return + */ + public TalkgroupIdentifier getSuperGroup() + { + if(mSupergroup == null) + { + mSupergroup = APCO25Talkgroup.create(getInt(ARGUMENTS)); + } + + return mSupergroup; + } + + /** + * Class + */ + public int getFunctionClass() + { + return getInt(CLASS); + } + + /** + * Operand + */ + public int getFunctionOperand() + { + return getInt(OPERAND); + } + + /** + * Arguments + */ + public int getFunctionArguments() + { + return getInt(ARGUMENTS); + } + + /** + * Requesting SU radio. + */ + public RadioIdentifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * Collective identifiers available in this message. + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + if(getFunctionClass() == 0x02) + { + mIdentifiers.add(getSuperGroup()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/MotorolaGroupRegroupVoiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/MotorolaGroupRegroupVoiceRequest.java new file mode 100644 index 000000000..55a914dd7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/MotorolaGroupRegroupVoiceRequest.java @@ -0,0 +1,131 @@ +/* + * ***************************************************************************** + * 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.tsbk.motorola.isp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorISPMessage; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.List; + +/** + * Inbound request for a channel group for a super-group sent by the SU. + */ +public class MotorolaGroupRegroupVoiceRequest extends VendorISPMessage implements IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_2_BIT_16); + private static final IntField SUPERGROUP = IntField.length16(OCTET_5_BIT_40); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_7_BIT_56); + + private VoiceServiceOptions mServiceOptions; + private TalkgroupIdentifier mSuperGroup; + private RadioIdentifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs an inbound (ISP) TSBK from the binary message sequence. + * + * @param dataUnitID TSBK1/2/3 + * @param message binary sequence + * @param nac decoded from the NID + * @param timestamp for the message + */ + public MotorolaGroupRegroupVoiceRequest(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitID, message, nac, timestamp); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + if(getServiceOptions().isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + + sb.append(" GROUP VOICE CALL FM:").append(getSourceAddress()); + sb.append(" TO:").append(getSuperGroup()); + + return super.toString(); + } + + public ServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + /** + * Super group for the call. + */ + public TalkgroupIdentifier getSuperGroup() + { + if(mSuperGroup == null) + { + mSuperGroup = APCO25Talkgroup.create(getInt(SUPERGROUP)); + } + + return mSuperGroup; + } + + /** + * Requesting SU radio. + */ + public RadioIdentifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * Collective identifiers available in this message. + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getSuperGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/UnknownMotorolaISPMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/UnknownMotorolaISPMessage.java index 8fc1c335c..ba2303b35 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/UnknownMotorolaISPMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/isp/UnknownMotorolaISPMessage.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.tsbk.motorola.isp; @@ -23,15 +22,14 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; - +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; import java.util.Collections; import java.util.List; /** - * Unknown/Unrecognized opcode message. + * Motorola Unknown/Unrecognized opcode message. */ -public class UnknownMotorolaISPMessage extends OSPMessage +public class UnknownMotorolaISPMessage extends VendorOSPMessage { /** * Constructs a TSBK from the binary message sequence. diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaAcknowledgeResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaAcknowledgeResponse.java new file mode 100644 index 000000000..26083f1a4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaAcknowledgeResponse.java @@ -0,0 +1,106 @@ +/* + * ***************************************************************************** + * 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.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.FragmentedIntField; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; +import io.github.dsheirer.module.decode.p25.reference.Direction; +import io.github.dsheirer.module.decode.p25.reference.Vendor; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Acknowledge response + */ +public class MotorolaAcknowledgeResponse extends VendorOSPMessage +{ + private static final FragmentedIntField SERVICE_TYPE = FragmentedIntField.of(18, 19, 20, 21, 22, 23); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_4_BIT_32); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_56); + + private Identifier mSourceAddress; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public MotorolaAcknowledgeResponse(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" ACKNOWLEDGE:").append(getAcknowledgedService()); + return sb.toString(); + } + + /** + * The acknowledged motorola (custom) opcode. + */ + public Opcode getAcknowledgedService() + { + return Opcode.fromValue(getInt(SERVICE_TYPE), Direction.OUTBOUND, Vendor.MOTOROLA); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaDenyResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaDenyResponse.java index 8ca5ccb11..92947fe55 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaDenyResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaDenyResponse.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -23,21 +23,23 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; import io.github.dsheirer.module.decode.p25.reference.DenyReason; import io.github.dsheirer.module.decode.p25.reference.Direction; import io.github.dsheirer.module.decode.p25.reference.Vendor; - import java.util.ArrayList; import java.util.List; /** - * Deny response + * Motorola Deny response */ -public class MotorolaDenyResponse extends OSPMessage +public class MotorolaDenyResponse extends VendorOSPMessage { - private static final int ADDITIONAL_INFORMATION_FLAG = 16; + //true = motorola custom reason code, false = standard reason code + private static final int RC = 16; + //true = non-motorola service type, false = motorola service type (ie opcode) + private static final int STP = 17; private static final int[] SERVICE_TYPE = {18, 19, 20, 21, 22, 23}; private static final int[] REASON = {24, 25, 26, 27, 28, 29, 30, 31}; private static final int[] ADDITIONAL_INFO = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, @@ -65,18 +67,13 @@ public String toString() sb.append(" TO:").append(getTargetAddress()); sb.append(" SERVICE:").append(getDeniedServiceType()); sb.append(" REASON:").append(getDenyReason()); - - if(hasAdditionalInformation()) - { - sb.append(" INFO:").append(getAdditionalInfo()); - } - + sb.append(" INFO:").append(getAdditionalInfo()); return sb.toString(); } private boolean hasAdditionalInformation() { - return getMessage().get(ADDITIONAL_INFORMATION_FLAG); + return getMessage().get(RC); } public String getAdditionalInfo() @@ -101,7 +98,15 @@ public DenyReason getDenyReason() { if(mDenyReason == null) { - mDenyReason = DenyReason.fromCode(getMessage().getInt(REASON)); + //When RC is true it indicates that the reason code is a custom vendor reason. + if(getMessage().get(RC)) + { + mDenyReason = DenyReason.fromCustomCode(getMessage().getInt(REASON), Vendor.MOTOROLA); + } + else + { + mDenyReason = DenyReason.fromCode(getMessage().getInt(REASON)); + } } return mDenyReason; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaEmergencyAlarmActivation.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaEmergencyAlarmActivation.java new file mode 100644 index 000000000..24bfa64e7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaEmergencyAlarmActivation.java @@ -0,0 +1,118 @@ +/* + * ***************************************************************************** + * 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.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Opcode 10 appears to be an Emergency Alarm Activation message. + * + * This was observed in the following sequence: + * 1. Radio registers on network. + * 2. Radio assigned group and affiliation group + * 3. Network acknowledges radio's emergency alarm request + * 4. Network sends this message, interspersed with the ack message (3) + * 5. Network grants emergency group voice channel to radio and the same talkgroup references in this message, which + * is also the assigned talkgroup during initial registration. + * 6. Network grants second non-emerg group voice channel to radio and a second (unknown 0x39) talkgroup that may be + * a supervisor talkgroup. Interesting that the radio is capable of dual-frequency operations?? + * + * Note: the same opcode is used on both on control channel TSBK and traffic channel LCW messaging. + */ +public class MotorolaEmergencyAlarmActivation extends VendorOSPMessage +{ + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_5_BIT_40); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_7_BIT_56); + private TalkgroupIdentifier mGroupAddress; + private RadioIdentifier mSourceAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public MotorolaEmergencyAlarmActivation(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" RADIO:").append(getSourceAddress()); + sb.append(" GROUP:").append(getGroupAddress()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + /** + * Group for the emergency call. + */ + public TalkgroupIdentifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + /** + * Source (activating) Address + */ + public RadioIdentifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + public int getUnknown() + { + return getInt(GROUP_ADDRESS); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaExtendedFunctionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaExtendedFunctionCommand.java new file mode 100644 index 000000000..614c180c5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaExtendedFunctionCommand.java @@ -0,0 +1,177 @@ +/* + * ***************************************************************************** + * 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.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola extended function command + */ +public class MotorolaExtendedFunctionCommand extends VendorOSPMessage +{ + private static final IntField CLASS = IntField.length8(OCTET_2_BIT_16); + private static final IntField OPERAND = IntField.length8(OCTET_3_BIT_24); + private static final IntField ARGUMENTS = IntField.length24(OCTET_4_BIT_32); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_56); + + private APCO25PatchGroup mSupergroup; + private RadioIdentifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs an outbound (OSP) TSBK from the binary message sequence. + * + * @param dataUnitID TSBK1/2/3 + * @param message binary sequence + * @param nac decoded from the NID + * @param timestamp for the message + */ + public MotorolaExtendedFunctionCommand(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitID, message, nac, timestamp); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" ").append(getDescription()); + sb.append(" FOR RADIO:").append(getTargetAddress()); + return super.toString(); + } + + public String getDescription() + { + if(isSupergroupCancel()) + { + return "CANCEL SUPERGROUP:" + getSuperGroup(); + } + else if(isSupergroupCreate()) + { + return "CREATE SUPERGROUP:" + getSuperGroup(); + } + else + { + return "UNRECOGNIZED EXTENDED FUNCTION CLASS:" + getFunctionClass() + " OPERAND:" + getFunctionOperand() + + " ARGUMENTS:" + getFunctionArguments(); + } + } + + /** + * Indicates if this is an supergroup creation command. + */ + public boolean isSupergroupCreate() + { + return getFunctionClass() == 0x02 && getFunctionOperand() == 0x00; + } + + /** + * Indicates if this is a supergroup cancel command. + */ + public boolean isSupergroupCancel() + { + return getFunctionClass() == 0x02 && getFunctionOperand() == 0x01; + } + + /** + * Supergroup / Patch Group + */ + public PatchGroupIdentifier getSuperGroup() + { + if(mSupergroup == null) + { + mSupergroup = APCO25PatchGroup.create(getInt(ARGUMENTS)); + + if(isSupergroupCreate()) + { + mSupergroup.getValue().addPatchedRadio(getTargetAddress()); + } + } + + return mSupergroup; + } + + /** + * Class + */ + public int getFunctionClass() + { + return getInt(CLASS); + } + + /** + * Operand + */ + public int getFunctionOperand() + { + return getInt(OPERAND); + } + + /** + * Arguments + */ + public int getFunctionArguments() + { + return getInt(ARGUMENTS); + } + + /** + * Requesting SU radio. + */ + public RadioIdentifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createFrom(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Collective identifiers available in this message. + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + if(getFunctionClass() == 0x02) + { + mIdentifiers.add(getSuperGroup()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupAdd.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupAddCommand.java similarity index 67% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupAdd.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupAddCommand.java index d2f077b56..c8918e7e6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupAdd.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupAddCommand.java @@ -1,44 +1,45 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.tsbk.motorola.osp; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; - +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; import java.util.ArrayList; import java.util.List; -public class PatchGroupAdd extends OSPMessage +/** + * Motorola Group Regroup Add Command + */ +public class MotorolaGroupRegroupAddCommand extends VendorOSPMessage { - public static final int[] PATCH_GROUP_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - public static final int[] GROUP_ADDRESS_1 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - public static final int[] GROUP_ADDRESS_2 = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - public static final int[] GROUP_ADDRESS_3 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final IntField PATCH_GROUP_ADDRESS = IntField.length16(OCTET_2_BIT_16); + private static final IntField GROUP_ADDRESS_1 = IntField.length16(OCTET_4_BIT_32); + private static final IntField GROUP_ADDRESS_2 = IntField.length16(OCTET_6_BIT_48); + private static final IntField GROUP_ADDRESS_3 = IntField.length16(OCTET_8_BIT_64); private APCO25PatchGroup mPatchGroup; private TalkgroupIdentifier mGroupAddress1; @@ -47,7 +48,7 @@ public class PatchGroupAdd extends OSPMessage private List mPatchedTalkgroups; private List mIdentifiers; - public PatchGroupAdd(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timeslot) + public MotorolaGroupRegroupAddCommand(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timeslot) { super(dataUnitID, message, nac, timeslot); } @@ -61,7 +62,7 @@ public String toString() return sb.toString(); } - public Identifier getPatchGroup() + public PatchGroupIdentifier getPatchGroup() { if(mPatchGroup == null) { @@ -75,7 +76,7 @@ public Identifier getPatchGroup() private int getPatchAddress() { - return getMessage().getInt(PATCH_GROUP_ADDRESS); + return getInt(PATCH_GROUP_ADDRESS); } @@ -124,7 +125,7 @@ public boolean hasGroupAddress1() private int getAddress1() { - return getMessage().getInt(GROUP_ADDRESS_1); + return getInt(GROUP_ADDRESS_1); } public TalkgroupIdentifier getGroupAddress2() @@ -139,7 +140,7 @@ public TalkgroupIdentifier getGroupAddress2() private int getAddress2() { - return getMessage().getInt(GROUP_ADDRESS_2); + return getInt(GROUP_ADDRESS_2); } public boolean hasGroupAddress2() @@ -159,7 +160,7 @@ public TalkgroupIdentifier getGroupAddress3() private int getAddress3() { - return getMessage().getInt(GROUP_ADDRESS_3); + return getInt(GROUP_ADDRESS_3); } public boolean hasGroupAddress3() diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupChannelGrant.java similarity index 73% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrant.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupChannelGrant.java index 1eb01e099..fa13a2c3a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupChannelGrant.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.tsbk.motorola.osp; @@ -26,19 +23,19 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.patch.PatchGroup; import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; -public class PatchGroupVoiceChannelGrant extends OSPMessage implements IFrequencyBandReceiver +public class MotorolaGroupRegroupChannelGrant extends VendorOSPMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { public static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; public static final int[] FREQUENCY_BAND = {24, 25, 26, 27}; @@ -53,7 +50,7 @@ public class PatchGroupVoiceChannelGrant extends OSPMessage implements IFrequenc private PatchGroupIdentifier mPatchGroup; private List mIdentifiers; - public PatchGroupVoiceChannelGrant(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestlot) + public MotorolaGroupRegroupChannelGrant(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestlot) { super(dataUnitID, message, nac, timestlot); } @@ -66,12 +63,12 @@ public String toString() sb.append(getMessageStub()); sb.append(" PATCH GROUP:").append(getPatchGroup()); sb.append(" FROM:").append(getSourceAddress()); - sb.append(" SERVICE OPTIONS:").append(getVoiceServiceOptions()); + sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupChannelUpdate.java similarity index 80% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrantUpdate.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupChannelUpdate.java index 318645cad..12d9f417b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupVoiceChannelGrantUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupChannelUpdate.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.tsbk.motorola.osp; @@ -32,11 +29,13 @@ import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; - import java.util.ArrayList; import java.util.List; -public class PatchGroupVoiceChannelGrantUpdate extends OSPMessage implements IFrequencyBandReceiver +/** + * Motorola group regroup channel update. + */ +public class MotorolaGroupRegroupChannelUpdate extends OSPMessage implements IFrequencyBandReceiver { public static final int[] FREQUENCY_BAND_1 = {16, 17, 18, 19}; public static final int[] CHANNEL_NUMBER_1 = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; @@ -51,7 +50,7 @@ public class PatchGroupVoiceChannelGrantUpdate extends OSPMessage implements IFr private APCO25Channel mChannel2; private List mIdentifiers; - public PatchGroupVoiceChannelGrantUpdate(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) + public MotorolaGroupRegroupChannelUpdate(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timestamp) { super(dataUnitID, message, nac, timestamp); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupDelete.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupDeleteCommand.java similarity index 64% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupDelete.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupDeleteCommand.java index 06dfba200..8d1c6cd8b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupDelete.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaGroupRegroupDeleteCommand.java @@ -1,44 +1,45 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.tsbk.motorola.osp; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; - +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; import java.util.ArrayList; import java.util.List; -public class PatchGroupDelete extends OSPMessage +/** + * Motorola Group Regroup Delete Command + */ +public class MotorolaGroupRegroupDeleteCommand extends VendorOSPMessage { - public static final int[] PATCH_GROUP_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - public static final int[] GROUP_ADDRESS_1 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - public static final int[] GROUP_ADDRESS_2 = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - public static final int[] GROUP_ADDRESS_3 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + private static final IntField PATCH_GROUP_ADDRESS = IntField.length16(OCTET_2_BIT_16); + private static final IntField GROUP_ADDRESS_1 = IntField.length16(OCTET_4_BIT_32); + private static final IntField GROUP_ADDRESS_2 = IntField.length16(OCTET_6_BIT_48); + private static final IntField GROUP_ADDRESS_3 = IntField.length16(OCTET_8_BIT_64); private APCO25PatchGroup mPatchGroup; private TalkgroupIdentifier mGroupAddress1; @@ -47,9 +48,17 @@ public class PatchGroupDelete extends OSPMessage private List mPatchedTalkgroups; private List mIdentifiers; - public PatchGroupDelete(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, long timeslot) + /** + * Constructs an instance + * @param dataUnitID for the TSBK + * @param message with fields + * @param nac code + * @param timestamp for this message + */ + public MotorolaGroupRegroupDeleteCommand(P25P1DataUnitID dataUnitID, CorrectedBinaryMessage message, int nac, + long timestamp) { - super(dataUnitID, message, nac, timeslot); + super(dataUnitID, message, nac, timestamp); } @Override @@ -61,7 +70,7 @@ public String toString() return sb.toString(); } - public Identifier getPatchGroup() + public PatchGroupIdentifier getPatchGroup() { if(mPatchGroup == null) { @@ -75,7 +84,7 @@ public Identifier getPatchGroup() private int getPatchAddress() { - return getMessage().getInt(PATCH_GROUP_ADDRESS); + return getInt(PATCH_GROUP_ADDRESS); } @@ -119,7 +128,7 @@ public TalkgroupIdentifier getGroupAddress1() private int getAddress1() { - return getMessage().getInt(GROUP_ADDRESS_1); + return getInt(GROUP_ADDRESS_1); } private boolean hasAddress1() @@ -139,7 +148,7 @@ public TalkgroupIdentifier getGroupAddress2() private int getAddress2() { - return getMessage().getInt(GROUP_ADDRESS_2); + return getInt(GROUP_ADDRESS_2); } public boolean hasGroupAddress2() @@ -160,7 +169,7 @@ public TalkgroupIdentifier getGroupAddress3() private int getAddress3() { - return getMessage().getInt(GROUP_ADDRESS_3); + return getInt(GROUP_ADDRESS_3); } public boolean hasGroupAddress3() diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaOpcode15.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaOpcode15.java new file mode 100644 index 000000000..e40227232 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaOpcode15.java @@ -0,0 +1,55 @@ +/* + * ***************************************************************************** + * 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.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Unknown Opcode 15 (0x0F). + */ +public class MotorolaOpcode15 extends VendorOSPMessage +{ + /** + * Constructs a TSBK from the binary message sequence. + */ + public MotorolaOpcode15(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaQueuedResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaQueuedResponse.java new file mode 100644 index 000000000..bc3082dee --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaQueuedResponse.java @@ -0,0 +1,130 @@ +/* + * ***************************************************************************** + * 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.tsbk.motorola.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage; +import io.github.dsheirer.module.decode.p25.reference.Direction; +import io.github.dsheirer.module.decode.p25.reference.QueuedResponseReason; +import io.github.dsheirer.module.decode.p25.reference.Vendor; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Queued Response + */ +public class MotorolaQueuedResponse extends VendorOSPMessage +{ + private static final int ADDITIONAL_INFORMATION_FLAG = 16; + private static final int[] SERVICE_TYPE = {18, 19, 20, 21, 22, 23}; + private static final int[] REASON = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] ADDITIONAL_INFO = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55}; + private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79}; + + private QueuedResponseReason mQueuedResponseReason; + private String mAdditionalInfo; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public MotorolaQueuedResponse(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" SERVICE:").append(getQueuedServiceType()); + sb.append(" REASON:").append(getQueuedResponseReason()); + + if(hasAdditionalInformation()) + { + sb.append(" INFO:").append(getAdditionalInfo()); + } + + return sb.toString(); + } + + private boolean hasAdditionalInformation() + { + return getMessage().get(ADDITIONAL_INFORMATION_FLAG); + } + + public String getAdditionalInfo() + { + if(mAdditionalInfo == null) + { + mAdditionalInfo = getMessage().getHex(ADDITIONAL_INFO, 6); + } + + return mAdditionalInfo; + } + + /** + * Opcode representing the service type that is being queued by the radio unit. + */ + public Opcode getQueuedServiceType() + { + return Opcode.fromValue(getMessage().getInt(SERVICE_TYPE), Direction.INBOUND, Vendor.STANDARD); + } + + public QueuedResponseReason getQueuedResponseReason() + { + if(mQueuedResponseReason == null) + { + mQueuedResponseReason = QueuedResponseReason.fromCode(getMessage().getInt(REASON)); + } + + return mQueuedResponseReason; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaTrafficChannel.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaTrafficChannel.java index 6dfdb8d9c..6462bd71e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaTrafficChannel.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/MotorolaTrafficChannel.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.tsbk.motorola.osp; @@ -25,7 +22,6 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; - import java.util.Collections; import java.util.List; @@ -40,8 +36,7 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - sb.append(" TRAFFIC CHANNEL "); - sb.append(getMessage().toHexString()); + sb.append(" ").append(getMessage().toHexString()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/GroupDataServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/GroupDataServiceRequest.java index ee475b99b..2757f766c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/GroupDataServiceRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/GroupDataServiceRequest.java @@ -24,6 +24,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; @@ -36,7 +37,7 @@ /** * Group data service request. */ -public class GroupDataServiceRequest extends ISPMessage +public class GroupDataServiceRequest extends ISPMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; @@ -63,14 +64,14 @@ public String toString() sb.append(getMessageStub()); sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getGroupAddress()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/GroupVoiceServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/GroupVoiceServiceRequest.java index 997460cd2..1d83165ae 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/GroupVoiceServiceRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/GroupVoiceServiceRequest.java @@ -24,6 +24,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; @@ -36,7 +37,7 @@ /** * Group voice call service request. */ -public class GroupVoiceServiceRequest extends ISPMessage +public class GroupVoiceServiceRequest extends ISPMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; @@ -63,14 +64,14 @@ public String toString() sb.append(getMessageStub()); sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getGroupAddress()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/IndividualDataServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/IndividualDataServiceRequest.java index be78f63f5..397642550 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/IndividualDataServiceRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/IndividualDataServiceRequest.java @@ -24,6 +24,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.ISPMessage; @@ -35,7 +36,7 @@ /** * Individual data service request. */ -public class IndividualDataServiceRequest extends ISPMessage +public class IndividualDataServiceRequest extends ISPMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; @@ -63,14 +64,14 @@ public String toString() sb.append(getMessageStub()); sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getTargetAddress()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/RoamingAddressResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/RoamingAddressResponse.java index 24f0bb93e..ea941977c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/RoamingAddressResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/RoamingAddressResponse.java @@ -1,35 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.tsbk.standard.isp; import io.github.dsheirer.bits.CorrectedBinaryMessage; 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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.ISPMessage; - import java.util.ArrayList; import java.util.List; @@ -45,9 +39,7 @@ public class RoamingAddressResponse extends ISPMessage private static final int[] SOURCE_ID = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private Identifier mWACN; - private Identifier mSystem; - private Identifier mSourceAddress; + private APCO25FullyQualifiedRadioIdentifier mRoamingAddress; private List mIdentifiers; /** @@ -62,10 +54,8 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - sb.append(" FM:").append(getSourceAddress()); - sb.append(" WACN:").append(getWACN()); - sb.append(" SYSTEM:").append(getSystem()); - sb.append(" MESSAGE NUMBER:").append(getMessageSequenceNumber()); + sb.append(" ROAMING AS:").append(getRoamingAddress()); + sb.append(" MESSAGE #").append(getMessageSequenceNumber()); if(isLastMessage()) { sb.append("-FINAL "); @@ -83,34 +73,17 @@ public int getMessageSequenceNumber() return getMessage().getInt(MESSAGE_SEQUENCE_NUMBER); } - public Identifier getWACN() - { - if(mWACN == null) - { - mWACN = APCO25Wacn.create(getMessage().getInt(WACN)); - } - - return mWACN; - } - - public Identifier getSystem() - { - if(mSystem == null) - { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM)); - } - - return mSystem; - } - - public Identifier getSourceAddress() + public Identifier getRoamingAddress() { - if(mSourceAddress == null) + if(mRoamingAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ID)); + int wacn = getMessage().getInt(WACN); + int system = getMessage().getInt(SYSTEM); + int id = getMessage().getInt(SOURCE_ID); + mRoamingAddress = APCO25FullyQualifiedRadioIdentifier.createTo(id, wacn, system, id); } - return mSourceAddress; + return mRoamingAddress; } @Override @@ -119,9 +92,7 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getWACN()); - mIdentifiers.add(getSystem()); - mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getRoamingAddress()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/TelephoneInterconnectAnswerResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/TelephoneInterconnectAnswerResponse.java index a02b47d85..fe56f77ae 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/TelephoneInterconnectAnswerResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/TelephoneInterconnectAnswerResponse.java @@ -24,6 +24,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.ISPMessage; @@ -36,7 +37,7 @@ /** * Telephone interconnect answer response */ -public class TelephoneInterconnectAnswerResponse extends ISPMessage +public class TelephoneInterconnectAnswerResponse extends ISPMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] ANSWER_RESPONSE = {24, 25, 26, 27, 28, 29, 30, 31}; @@ -63,14 +64,14 @@ public String toString() sb.append(getMessageStub()); sb.append(" FM:").append(getSourceAddress()); sb.append(" RESPONSE:").append(getAnswerResponse()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/TelephoneInterconnectPstnRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/TelephoneInterconnectPstnRequest.java index 360dcadff..efa4e13b5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/TelephoneInterconnectPstnRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/TelephoneInterconnectPstnRequest.java @@ -24,6 +24,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.telephone.APCO25TelephoneNumber; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; @@ -37,7 +38,7 @@ * Implicit Dialing Telephone Interconnect request. The telephone number (PSTN) is a predefined system value and is * identified by the PSTN address value. */ -public class TelephoneInterconnectPstnRequest extends ISPMessage +public class TelephoneInterconnectPstnRequest extends ISPMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, @@ -65,14 +66,14 @@ public String toString() sb.append(getMessageStub()); sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getPstnAddress()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/UnitToUnitVoiceServiceAnswerResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/UnitToUnitVoiceServiceAnswerResponse.java index 7a5f8a758..6f0191ec7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/UnitToUnitVoiceServiceAnswerResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/UnitToUnitVoiceServiceAnswerResponse.java @@ -24,6 +24,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.ISPMessage; @@ -36,7 +37,7 @@ /** * Response to a unit-to-unit answer request */ -public class UnitToUnitVoiceServiceAnswerResponse extends ISPMessage +public class UnitToUnitVoiceServiceAnswerResponse extends ISPMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] ANSWER_RESPONSE = {24, 25, 26, 27, 28, 29, 30, 31}; @@ -65,14 +66,14 @@ public String toString() sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getTargetAddress()); sb.append(" RESPONSE:").append(getAnswerResponse()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/UnitToUnitVoiceServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/UnitToUnitVoiceServiceRequest.java index e8a85eb90..d30472657 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/UnitToUnitVoiceServiceRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/isp/UnitToUnitVoiceServiceRequest.java @@ -1,41 +1,38 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.tsbk.standard.isp; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.ISPMessage; import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** - * Voice call service request between two specified subscriber units. + * Voice call service request between two subscriber units. */ -public class UnitToUnitVoiceServiceRequest extends ISPMessage +public class UnitToUnitVoiceServiceRequest extends ISPMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; @@ -63,14 +60,14 @@ public String toString() sb.append(getMessageStub()); sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getTargetId()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/AcknowledgeResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/AcknowledgeResponse.java index b2935ad29..820d72fab 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/AcknowledgeResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/AcknowledgeResponse.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; import io.github.dsheirer.module.decode.p25.reference.Direction; import io.github.dsheirer.module.decode.p25.reference.Vendor; - import java.util.ArrayList; import java.util.List; @@ -71,7 +70,7 @@ public String toString() sb.append(" FM:").append(getSourceAddress()); } sb.append(" TO:").append(getTargetAddress()); - sb.append(" ACKNOWLEDGING:").append(getAcknowledgedServiceType()); + sb.append(" ACKNOWLEDGING:").append(getAcknowledgedService()); if(hasWACN()) { sb.append(" WACN:").append(getWACN()); @@ -136,7 +135,7 @@ public Identifier getSystemId() /** * Opcode representing the service type that is being acknowledged by the radio unit. */ - public Opcode getAcknowledgedServiceType() + public Opcode getAcknowledgedService() { return Opcode.fromValue(getMessage().getInt(SERVICE_TYPE), Direction.INBOUND, Vendor.STANDARD); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrant.java index dac46de6e..28642fe23 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrant.java @@ -25,6 +25,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; @@ -39,7 +40,7 @@ /** * Group voice call channel grant. */ -public class GroupVoiceChannelGrant extends OSPMessage implements IFrequencyBandReceiver +public class GroupVoiceChannelGrant extends OSPMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] FREQUENCY_BAND = {24, 25, 26, 27}; @@ -69,14 +70,14 @@ public String toString() sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getGroupAddress()); sb.append(" CHAN:").append(getChannel()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdate.java index 463476b4c..9d7582ed0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdate.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.tsbk.standard.osp; @@ -30,7 +27,6 @@ import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; - import java.util.ArrayList; import java.util.List; @@ -65,11 +61,11 @@ public String toString() StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); sb.append(" GROUP A:").append(getGroupAddressA()); - sb.append(" CHAN:").append(getChannelA()); + sb.append(" CHAN A:").append(getChannelA()); if(hasGroupB()) { sb.append(" GROUP B:").append(getGroupAddressB()); - sb.append(" CHAN:").append(getChannelB()); + sb.append(" CHAN B:").append(getChannelB()); } return sb.toString(); } @@ -143,7 +139,12 @@ public List getChannels() { List channels = new ArrayList<>(); channels.add(getChannelA()); - channels.add(getChannelB()); + + if(hasGroupB()) + { + channels.add(getChannelB()); + } + return channels; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdateExplicit.java index ff8fd2f48..2939a0cfe 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdateExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/GroupVoiceChannelGrantUpdateExplicit.java @@ -25,6 +25,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; @@ -38,7 +39,7 @@ /** * Group voice call channel grant. */ -public class GroupVoiceChannelGrantUpdateExplicit extends OSPMessage implements IFrequencyBandReceiver +public class GroupVoiceChannelGrantUpdateExplicit extends OSPMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; @@ -67,14 +68,14 @@ public String toString() sb.append(getMessageStub()); sb.append(" TALKGROUP:").append(getGroupAddress()); sb.append(" CHAN:").append(getChannel()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/NetworkStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/NetworkStatusBroadcast.java index 3d349bc8c..546432410 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/NetworkStatusBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/NetworkStatusBroadcast.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.tsbk.standard.osp; @@ -34,7 +31,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.List; @@ -56,7 +52,6 @@ public class NetworkStatusBroadcast extends OSPMessage implements IFrequencyBand private IChannelDescriptor mChannel; private SystemServiceClass mSystemServiceClass; private List mIdentifiers; - private List mSiteFlags; private ScrambleParameters mScrambleParameters; /** @@ -74,6 +69,7 @@ public String toString() sb.append(" WACN:").append(getWacn()); sb.append(" SYSTEM:").append(getSystem()); sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" CHAN:").append(getChannel()); sb.append(" SERVICES:").append(getSystemServiceClass().getServices()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/RFSSStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/RFSSStatusBroadcast.java index 5ea9a8119..5f8af0a04 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/RFSSStatusBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/RFSSStatusBroadcast.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.tsbk.standard.osp; @@ -34,7 +31,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.List; @@ -76,6 +72,7 @@ public String toString() sb.append(" RFSS:").append(getRfss()); sb.append(" SITE:").append(getSite()); sb.append(" LRA:").append(getLocationRegistrationArea()); + sb.append(" CHAN:").append(getChannel()); if(isActiveNetworkConnectionToRfssControllerSite()) { sb.append(" ACTIVE NETWORK CONNECTION"); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/SyncBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/SynchronizationBroadcast.java similarity index 97% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/SyncBroadcast.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/SynchronizationBroadcast.java index 449597580..dce04caf1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/SyncBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/SynchronizationBroadcast.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.tsbk.standard.osp; @@ -24,7 +23,6 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; - import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -35,9 +33,9 @@ import java.util.TimeZone; /** - * TDMA sync broadcast + * Synchronization broadcast */ -public class SyncBroadcast extends OSPMessage +public class SynchronizationBroadcast extends OSPMessage { public static final int[] RESERVED = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28}; public static final int SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE_FLAG = 29; @@ -60,7 +58,7 @@ public class SyncBroadcast extends OSPMessage /** * Constructs a TSBK from the binary message sequence. */ - public SyncBroadcast(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + public SynchronizationBroadcast(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) { super(dataUnitId, message, nac, timestamp); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrant.java index 0d287a4bc..08a2323da 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrant.java @@ -25,6 +25,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; @@ -38,7 +39,7 @@ /** * Telephone interconnect voice call channel grant. */ -public class TelephoneInterconnectVoiceChannelGrant extends OSPMessage implements IFrequencyBandReceiver +public class TelephoneInterconnectVoiceChannelGrant extends OSPMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] FREQUENCY_BAND = {24, 25, 26, 27}; @@ -67,14 +68,14 @@ public String toString() sb.append(" RADIO:").append(getSourceAddress()); sb.append(" TIMER:").append(getCallTimer()); sb.append("ms CHAN:").append(getChannel()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrantUpdate.java index a9b02ccc5..795dd9af8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrantUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/TelephoneInterconnectVoiceChannelGrantUpdate.java @@ -25,6 +25,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; @@ -38,7 +39,7 @@ /** * Telephone interconnect voice call channel grant. */ -public class TelephoneInterconnectVoiceChannelGrantUpdate extends OSPMessage implements IFrequencyBandReceiver +public class TelephoneInterconnectVoiceChannelGrantUpdate extends OSPMessage implements IFrequencyBandReceiver, IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] FREQUENCY_BAND = {24, 25, 26, 27}; @@ -67,14 +68,14 @@ public String toString() sb.append(" RADIO:").append(getAnyAddress()); sb.append(" TIMER:").append(getCallTimer()); sb.append("ms CHAN:").append(getChannel()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitDeRegistrationAcknowledge.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitDeRegistrationAcknowledge.java index 7090330e0..4c3839792 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitDeRegistrationAcknowledge.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitDeRegistrationAcknowledge.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.tsbk.standard.osp; @@ -29,7 +26,6 @@ import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; - import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitRegistrationResponse.java index fbda300c0..f2e04e135 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitRegistrationResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitRegistrationResponse.java @@ -1,35 +1,31 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.tsbk.standard.osp; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.APCO25System; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; import io.github.dsheirer.module.decode.p25.reference.Response; - import java.util.ArrayList; import java.util.List; @@ -41,15 +37,13 @@ public class UnitRegistrationResponse extends OSPMessage private static final int[] RESERVED = {16, 17}; private static final int[] RESPONSE = {18, 19}; private static final int[] SYSTEM_ID = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] TARGET_UNIQUE_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + private static final int[] SOURCE_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; private Response mResponse; - private Identifier mSystem; - private Identifier mTargetUniqueId; - private Identifier mTargetAddress; + private Identifier mRegisteredRadio; private List mIdentifiers; /** @@ -64,51 +58,41 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); - sb.append(" TO ADDR:").append(getTargetAddress()); - sb.append(" TO ID:").append(getTargetUniqueId()); - sb.append(" SYSTEM:").append(getSystem()); - sb.append(" RESPONSE:").append(getResponse()); + sb.append(" REGISTRATION ").append(getResponse().name()); + sb.append(" FOR RADIO:").append(getRegisteredRadio()); return sb.toString(); } - public Response getResponse() - { - if(mResponse == null) - { - mResponse = Response.fromValue(getMessage().getInt(RESPONSE)); - } - - return mResponse; - } - - public Identifier getSystem() + public Identifier getRegisteredRadio() { - if(mSystem == null) + if(mRegisteredRadio == null) { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID)); + int id = getMessage().getInt(SOURCE_ID); + int localAddress = getMessage().getInt(SOURCE_ADDRESS); + + if(id == localAddress || localAddress == 0) + { + mRegisteredRadio = APCO25RadioIdentifier.createTo(id); + } + else + { + int wacn = 0; //We don't know what the wacn is from this message. + int systemId = getMessage().getInt(SYSTEM_ID); + mRegisteredRadio = APCO25FullyQualifiedRadioIdentifier.createTo(localAddress, wacn, systemId, id); + } } - return mSystem; + return mRegisteredRadio; } - public Identifier getTargetUniqueId() - { - if(mTargetUniqueId == null) - { - mTargetUniqueId = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_UNIQUE_ID)); - } - - return mTargetUniqueId; - } - - public Identifier getTargetAddress() + public Response getResponse() { - if(mTargetAddress == null) + if(mResponse == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + mResponse = Response.fromValue(getMessage().getInt(RESPONSE)); } - return mTargetAddress; + return mResponse; } @Override @@ -117,9 +101,7 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetUniqueId()); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSystem()); + mIdentifiers.add(getRegisteredRadio()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitToUnitAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitToUnitAnswerRequest.java index 9a1b40eb9..35c2923a1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitToUnitAnswerRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/standard/osp/UnitToUnitAnswerRequest.java @@ -24,6 +24,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; @@ -35,7 +36,7 @@ /** * Unit to Unit answer request */ -public class UnitToUnitAnswerRequest extends OSPMessage +public class UnitToUnitAnswerRequest extends OSPMessage implements IServiceOptionsProvider { private static final int[] SERVICE_OPTIONS = {16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] RESERVED = {24, 25, 26, 27, 28, 29, 30, 31}; @@ -63,14 +64,14 @@ public String toString() sb.append(getMessageStub()); sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getTargetAddress()); - sb.append(" ").append(getVoiceServiceOptions().toString()); + sb.append(" ").append(getServiceOptions().toString()); return sb.toString(); } /** * Service options for the request */ - public VoiceServiceOptions getVoiceServiceOptions() + public VoiceServiceOptions getServiceOptions() { if(mVoiceServiceOptions == null) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/vselp/VSELP1Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/vselp/VSELP1Message.java index 5da1e6883..da7bb65fb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/vselp/VSELP1Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/vselp/VSELP1Message.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.vselp; @@ -23,12 +22,11 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; - +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import java.util.Collections; import java.util.List; -public class VSELP1Message extends P25Message +public class VSELP1Message extends P25P1Message { /** * Motorola VSELP audio message 1 - not implemented. diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/vselp/VSELP2Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/vselp/VSELP2Message.java index 8f71a4686..98ff61c43 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/vselp/VSELP2Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/vselp/VSELP2Message.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.vselp; @@ -23,12 +22,11 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.P25Message; - +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import java.util.Collections; import java.util.List; -public class VSELP2Message extends P25Message +public class VSELP2Message extends P25P1Message { /** * Motorola VSELP audio message - not implemented. diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/DecodeConfigP25Phase2.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/DecodeConfigP25Phase2.java index 522d7617c..14e8e9a3f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/DecodeConfigP25Phase2.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/DecodeConfigP25Phase2.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2; @@ -25,11 +22,15 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.config.DecodeConfiguration; +import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; +import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; import io.github.dsheirer.source.tuner.channel.ChannelSpecification; -public class DecodeConfigP25Phase2 extends DecodeConfiguration +/** + * APCO25 Phase 2 decoder configuration + */ +public class DecodeConfigP25Phase2 extends DecodeConfigP25 { private ScrambleParameters mScrambleParameters; private boolean mAutoDetectScrambleParameters; @@ -54,6 +55,9 @@ public ChannelSpecification getChannelSpecification() return new ChannelSpecification(50000.0, 12500, 6500.0, 7200.0); } + /** + * Number of timeslots for this protocol. + */ @Override public int getTimeslotCount() { @@ -63,14 +67,11 @@ public int getTimeslotCount() @Override public int[] getTimeslots() { - int[] timeslots = new int[2]; - timeslots[0] = 0; - timeslots[1] = 1; - return timeslots; + return new int[]{P25P2Message.TIMESLOT_1, P25P2Message.TIMESLOT_2}; } /** - * Optional scramble (ie randomizer) parameters to use for the channel. + * Optional user-provided scramble (ie randomizer) parameters to use for the channel. */ @JacksonXmlProperty(localName = "scramble_parameters") public ScrambleParameters getScrambleParameters() @@ -78,17 +79,26 @@ public ScrambleParameters getScrambleParameters() return mScrambleParameters; } + /** + * Sets the user-provided scramble parameters + */ public void setScrambleParameters(ScrambleParameters scrambleParameters) { mScrambleParameters = scrambleParameters; } + /** + * Indicates if the decoder will attempt to auto-detect scramble parameters. + */ @JacksonXmlProperty(isAttribute = true, localName = "auto_detect_scramble_parameters") public boolean isAutoDetectScrambleParameters() { return mAutoDetectScrambleParameters; } + /** + * Sets the decoder to attempt to auto-detect scramble parameters. + */ public void setAutoDetectScrambleParameters(boolean autoDetect) { mAutoDetectScrambleParameters = autoDetect; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java index 2d272e696..114113854 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2Decoder.java @@ -18,11 +18,13 @@ */ package io.github.dsheirer.module.decode.p25.phase2; +import com.google.common.eventbus.Subscribe; import io.github.dsheirer.dsp.squelch.PowerMonitor; import io.github.dsheirer.dsp.symbol.Dibit; import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.FeedbackDecoder; +import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent; import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.buffer.IByteBufferProvider; @@ -54,6 +56,18 @@ public P25P2Decoder(double symbolRate) getDibitBroadcaster().addListener(mByteBufferAssembler); } + /** + * Preloads P25 frequency bands when this is a traffic channel, since they are not transmitted on the traffic + * channel and the decoder state needs accurate frequency values to pass to the traffic channel manager for + * call event tracking. + * @param preLoadDataContent with known frequency bands. + */ + @Subscribe + public void process(P25FrequencyBandPreloadDataContent preLoadDataContent) + { + mMessageProcessor.preload(preLoadDataContent); + } + @Override public void setSourceEventListener(Listener listener ) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java index 1c2d949d1..b96287700 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSK.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -18,6 +18,10 @@ */ package io.github.dsheirer.module.decode.p25.phase2; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.channel.state.MultiChannelState; +import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.dsp.filter.FilterFactory; import io.github.dsheirer.dsp.filter.design.FilterDesignException; import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification; @@ -32,12 +36,24 @@ import io.github.dsheirer.identifier.Form; import io.github.dsheirer.identifier.IdentifierUpdateListener; import io.github.dsheirer.identifier.IdentifierUpdateNotification; +import io.github.dsheirer.identifier.patch.PatchGroupManager; +import io.github.dsheirer.module.ProcessingChain; import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager; +import io.github.dsheirer.module.decode.p25.audio.P25P2AudioModule; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; +import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; +import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.complex.ComplexSamples; import io.github.dsheirer.source.SourceEvent; +import io.github.dsheirer.source.wave.ComplexWaveSource; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -225,4 +241,121 @@ public void receive(IdentifierUpdateNotification identifierUpdateNotification) } }; } + + public static void main(String[] args) + { + UserPreferences userPreferences = new UserPreferences(); + userPreferences.getJmbeLibraryPreference().setPathJmbeLibrary(Path.of("/home/denny/SDRTrunk/jmbe/jmbe-1.0.9.jar")); + + Channel channel = new Channel("Phase 2 Test", Channel.ChannelType.TRAFFIC); + DecodeConfigP25Phase2 config = new DecodeConfigP25Phase2(); + channel.setDecodeConfiguration(config); + + Path path = Paths.get("/home/denny/temp/20240309_054833_855787500_Duke_Energy_Monroe_Dicks_Creek_Site_201-24_1_baseband.wav"); +// ScrambleParameters scrambleParameters = new ScrambleParameters(0x91F14, 0x201, 0x00A); +// config.setScrambleParameters(scrambleParameters); + +// Path path = Paths.get("/home/denny/temp/PA-STARNet_ECEN_TRAFFIC_10_baseband_20220610_104744.wav"); +// ScrambleParameters scrambleParameters = new ScrambleParameters(781824, 2370, 2369); +// config.setScrambleParameters(scrambleParameters); + +// Path path = Paths.get("/home/denny/temp/20240303_203244_859412500_Duke_Energy_P25_Lake_Control_28_baseband.wav"); +// ScrambleParameters scrambleParameters = new ScrambleParameters(0x91F14, 0x2D7, 0x00A); +// config.setScrambleParameters(scrambleParameters); + + AliasList aliasList = new AliasList("bogus"); + ProcessingChain processingChain = new ProcessingChain(channel, new AliasModel()); + P25TrafficChannelManager trafficChannelManager = new P25TrafficChannelManager(channel); + PatchGroupManager patchGroupManager = new PatchGroupManager(); + P25P2DecoderState ds1 = new P25P2DecoderState(channel, P25P2Message.TIMESLOT_1, trafficChannelManager, + patchGroupManager); +// Listener listener = event -> mLog.info("\n>>>>>>> Event: " + event + "\n"); +// ds1.addDecodeEventListener(listener); + ds1.start(); + P25P2DecoderState ds2 = new P25P2DecoderState(channel, P25P2Message.TIMESLOT_2, trafficChannelManager, + patchGroupManager); +// ds2.addDecodeEventListener(listener); + ds2.start(); + processingChain.addModule(ds1); + processingChain.addModule(ds2); + P25P2AudioModule am1 = new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_1, aliasList); + am1.start(); + P25P2AudioModule am2 = new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_2, aliasList); + am2.start(); + processingChain.addModule(am1); + processingChain.addModule(am2); +// processingChain.addAudioSegmentListener(audioSegment -> mLog.info("**** Audio Segment *** " + audioSegment)); + MultiChannelState multiChannelState = new MultiChannelState(channel, null, config.getTimeslots()); + multiChannelState.start(); +// Broadcaster squelchBroadcaster = new Broadcaster<>(); +// multiChannelState.setSquelchStateListener(squelchBroadcaster); +// squelchBroadcaster.addListener(am1.getSquelchStateListener()); +// squelchBroadcaster.addListener(am2.getSquelchStateListener()); + + try(ComplexWaveSource source = new ComplexWaveSource(path.toFile(), false)) + { + P25P2DecoderHDQPSK decoder = new P25P2DecoderHDQPSK(config); + decoder.setMessageListener(message -> { + mLog.info(message.toString()); + + if(message.getTimeslot() == P25P2Message.TIMESLOT_1) + { + ds1.receive(message); + am1.receive(message); + } + else + { + ds2.receive(message); + am2.receive(message); + } + + if(message.toString().contains("GPS LOCATION")) + { + if(message instanceof MacMessage mac) + { + mLog.warn("Mac Structure Class: " + mac.getMacStructure().getClass()); + mLog.warn("Opcode:" + mac.getMacStructure().getOpcode().name()); + } + else + { + mLog.warn("Class: " + message.getClass()); + } + } + }); + source.setSourceEventListener(decoder.getSourceEventListener()); + + source.setListener(iNativeBuffer -> { + Iterator it = iNativeBuffer.iterator(); + while(it.hasNext()) + { + ComplexSamples samples = it.next(); + decoder.receive(samples); + } + }); + + source.open(); + decoder.setSampleRate(source.getSampleRate()); + decoder.start(); + + while(true) + { + source.next(2048, true); + } + } + catch(IOException ioe) + { + if(ioe.getMessage().contains("End of file reached")) + { + mLog.info("End of file"); + } + else + { + mLog.error("I/O Error", ioe); + } + } + catch(Exception ioe) + { + mLog.error("Error", ioe); + } + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java index 8c47c1132..6b4c8e2fa 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java @@ -37,89 +37,129 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; import io.github.dsheirer.identifier.patch.PatchGroupManager; import io.github.dsheirer.identifier.patch.PatchGroupPreLoadDataContent; +import io.github.dsheirer.log.LoggingSuppressor; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.DecodeEvent; import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.event.PlottableDecodeEvent; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; import io.github.dsheirer.module.decode.p25.P25DecodeEvent; +import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacPduType; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AcknowledgeResponse; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.CallAlertExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AcknowledgeResponseFNEAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AcknowledgeResponseFNEExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AuthenticationDemand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AuthenticationFNEResponseAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AuthenticationFNEResponseExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.DenyResponse; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.EndPushToTalk; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommand; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommandExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommandAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommandExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommandExtendedVCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupAffiliationQueryAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupAffiliationQueryExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupAffiliationResponseAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupAffiliationResponseExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupRegroupVoiceChannelUserAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateExplicit; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateMultiple; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateMultipleExplicit; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceServiceRequest; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.IndividualPagingMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateMultipleImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.IndirectGroupPagingWithoutPriority; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.IndividualPagingWithPriority; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.LocationRegistrationResponse; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacRelease; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MessageUpdateAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MessageUpdateExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MessageUpdateExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MessageUpdateExtendedVCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PowerControlSignalQuality; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PushToTalk; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.QueuedResponse; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommand; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandEnhanced; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SNDCPDataChannelGrant; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandExtendedVCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorEnhancedCommandAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorEnhancedCommandExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RfssStatusBroadcastImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RoamingAddressCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RoamingAddressUpdate; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SNDCPDataPageRequest; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusQueryAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusQueryExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusQueryExtendedVCH; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusUpdateAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusUpdateExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusUpdateExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusUpdateExtendedVCH; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectAnswerRequest; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelGrantUpdateExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelGrantUpdateImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitAnswerRequestAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitAnswerRequestExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisGpsLocation; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisRegroupCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateExtendedVCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisGroupRegroupExplicitEncryptionCommand; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisTalkerAlias; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisTalkerGpsLocation; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaAcknowledgeResponse; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaDenyResponse; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupAddCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupDeleteCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupExtendedFunctionCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUpdate; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaQueuedResponse; import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; import io.github.dsheirer.protocol.Protocol; -import java.util.Date; +import java.util.Collections; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Decoder state for an APCO25 Phase II channel. Maintains the call/data/idle state of the channel and produces events - * by monitoring the decoded message stream. - * + * Decoder state for an APCO-25 Phase II channel. Maintains the call/control/data/idle state of the channel and + * produces events by monitoring the decoded message stream. */ public class P25P2DecoderState extends TimeslotDecoderState implements IdentifierUpdateListener { - private final static Logger mLog = LoggerFactory.getLogger(P25P2DecoderState.class); - private static int SYSTEM_CONTROLLER = 0xFFFFFF; - private ChannelType mChannelType; - private PatchGroupManager mPatchGroupManager = new PatchGroupManager(); + private static final Logger LOGGER = LoggerFactory.getLogger(P25P2DecoderState.class); + private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LOGGER); + private Channel mChannel; + private PatchGroupManager mPatchGroupManager; private P25P2NetworkConfigurationMonitor mNetworkConfigurationMonitor = new P25P2NetworkConfigurationMonitor(); - private DecodeEvent mCurrentCallEvent; + private P25TrafficChannelManager mTrafficChannelManager; private int mEndPttOnFacchCounter = 0; /** - * Constructs an APCO-25 decoder state for a traffic channel. + * Constructs an APCO-25 decoder state instance for a traffic or control channel. + * * @param channel with configuration details + * @param timeslot for this decoder state + * @param trafficChannelManager to coordinate traffic channel activity + * @param patchGroupManager instance shared across both timeslots */ - public P25P2DecoderState(Channel channel, int timeslot) + public P25P2DecoderState(Channel channel, int timeslot, P25TrafficChannelManager trafficChannelManager, + PatchGroupManager patchGroupManager) { super(timeslot); - mChannelType = channel.getChannelType(); + mChannel = channel; + mTrafficChannelManager = trafficChannelManager; + mPatchGroupManager = patchGroupManager; } /** @@ -147,22 +187,27 @@ public void reset() protected void resetState() { super.resetState(); - closeCurrentCallEvent(System.currentTimeMillis(), true, MacPduType.MAC_3_IDLE); + closeCurrentCallEvent(System.currentTimeMillis(), true, false); mEndPttOnFacchCounter = 0; } /** * Processes an identifier collection to harvest Patch Groups to preload when this channel is first starting up. + * * @param preLoadDataContent containing an identifier collection with optional patch group identifier(s). */ @Subscribe public void process(PatchGroupPreLoadDataContent preLoadDataContent) { - for(Identifier identifier: preLoadDataContent.getData().getIdentifiers(Role.TO)) + //Only do this on timeslot 1 since both timeslots are sharing the same patch group manager. + if(getTimeslot() == P25P1Message.TIMESLOT_1) { - if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier) + for(Identifier identifier : preLoadDataContent.getData().getIdentifiers(Role.TO)) { - mPatchGroupManager.addPatchGroup(patchGroupIdentifier); + if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier) + { + mPatchGroupManager.addPatchGroup(patchGroupIdentifier, preLoadDataContent.getTimestamp()); + } } } } @@ -178,42 +223,33 @@ public void receive(IMessage message) if(message instanceof MacMessage macMessage) { processMacMessage(macMessage); - - MacPduType macPduType = macMessage.getMacPduType(); - - //Ignore End PTT - this is handled in the processMacMessage() method - if(macPduType != MacPduType.MAC_2_END_PTT) - { - mEndPttOnFacchCounter = 0; - continueState(getStateFromPduType(macPduType)); - } - - if(macPduType == MacPduType.MAC_3_IDLE) - { - closeCurrentCallEvent(message.getTimestamp(), true, macPduType); - } } else if(message instanceof AbstractVoiceTimeslot) { - if(mCurrentCallEvent != null) + if(isEncrypted()) { - if(isEncrypted()) - { - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED, getTimeslot())); - } - else - { - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot())); - } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED, getTimeslot())); + } + else + { + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot())); } - - updateCurrentCall(null, null, message.getTimestamp()); } - else if(message instanceof EncryptionSynchronizationSequence) + else if(message instanceof EncryptionSynchronizationSequence ess) { //We don't send any state events for this message since it can only occur in conjunction with //an audio frame that already sends the call state event getIdentifierCollection().update(message.getIdentifiers()); + mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), ess.getEncryptionKey(), ess.getTimestamp()); + + if(ess.isEncrypted()) + { + continueState(State.ENCRYPTED); + } + else + { + continueState(State.CALL); + } } } } @@ -225,16 +261,17 @@ private static State getStateFromPduType(MacPduType macPduType) { switch(macPduType) { + case MAC_0_SIGNAL: + return State.CONTROL; case MAC_1_PTT: return State.CALL; case MAC_2_END_PTT: return State.TEARDOWN; case MAC_4_ACTIVE: case MAC_6_HANGTIME: - return State.ACTIVE; case MAC_3_IDLE: default: - return State.IDLE; + return State.ACTIVE; } } @@ -249,870 +286,1613 @@ private void broadcastCurrentChannel(APCO25Channel channel) getIdentifierCollection().update(channel); } + /** + * Process MAC message structures + */ private void processMacMessage(MacMessage message) { - mNetworkConfigurationMonitor.processMacMessage(message); + MacPduType macPduType = message.getMacPduType(); + + switch(macPduType) + { + case MAC_0_SIGNAL: + continueState(State.CONTROL); + break; + } MacStructure mac = message.getMacStructure(); switch((mac.getOpcode())) { + /** + * Partition 0 Opcodes + */ + case TDMA_PARTITION_0_UNKNOWN_OPCODE: + break; case PUSH_TO_TALK: processPushToTalk(message, mac); break; case END_PUSH_TO_TALK: processEndPushToTalk(message, mac); break; - case TDMA_0_NULL_INFORMATION_MESSAGE: - MacPduType type = message.getMacPduType(); + case TDMA_00_NULL_INFORMATION_MESSAGE: + processNullInformation(message, mac); + break; + case TDMA_01_GROUP_VOICE_CHANNEL_USER_ABBREVIATED: + case TDMA_02_UNIT_TO_UNIT_VOICE_CHANNEL_USER_ABBREVIATED: + case TDMA_03_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: + processChannelUser(message, mac); + break; + case TDMA_05_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_IMPLICIT: + processChannelGrantUpdate(message, mac); + break; + case TDMA_08_NULL_AVOID_ZERO_BIAS: + //This is a filler message - ignore + break; + case TDMA_10_MULTI_FRAGMENT_CONTINUATION_MESSAGE: + //Ignore - this is a continuation message that doesn't mean anything by itself + break; + case TDMA_11_INDIRECT_GROUP_PAGING_WITHOUT_PRIORITY: + case TDMA_12_INDIVIDUAL_PAGING_WITH_PRIORITY: + processPaging(message, mac); + break; + case TDMA_21_GROUP_VOICE_CHANNEL_USER_EXTENDED: + case TDMA_22_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: + processChannelUser(message, mac); + break; + case TDMA_25_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT: + processChannelGrantUpdate(message, mac); + break; + case TDMA_30_POWER_CONTROL_SIGNAL_QUALITY: + processPowerControl(message, mac); + break; + case TDMA_31_MAC_RELEASE: + processMacRelease(message, mac); + break; - if(type == MacPduType.MAC_3_IDLE || type == MacPduType.MAC_6_HANGTIME) - { - closeCurrentCallEvent(message.getTimestamp(), true, type); - } + /** + * Partition 1 Opcodes + */ + case PHASE1_PARTITION_1_UNKNOWN_OPCODE: break; - case TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED: - processGVCUAbbreviated(message, mac); + case PHASE1_40_GROUP_VOICE_CHANNEL_GRANT_IMPLICIT: + processChannelGrant(message, mac); break; - case TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED: - processGVCUExtended(message, mac); + case PHASE1_41_GROUP_VOICE_SERVICE_REQUEST: + //Inbound only. break; - case TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER: - processUTUVCU(message, mac); + case PHASE1_42_GROUP_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: + processChannelGrantUpdate(message, mac); break; - case TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: - processUTUVCUExtended(message, mac); + case PHASE1_44_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_ABBREVIATED: + processChannelGrant(message, mac); break; - case TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: - processTIVCU(message, mac); + case PHASE1_45_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED: + processAnswer(message, mac); break; - case TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE: - processGVCGUM(mac); + case PHASE1_46_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED: + processChannelGrantUpdate(message, mac); break; - case TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT: - processGVCGUMExplicit(mac); + case PHASE1_48_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_IMPLICIT: + processChannelGrant(message, mac); break; - case PHASE1_64_GROUP_VOICE_CHANNEL_GRANT_ABBREVIATED: - processGVCGAbbreviated(mac); + case PHASE1_49_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: + processChannelGrantUpdate(message, mac); break; - case PHASE1_66_GROUP_VOICE_CHANNEL_GRANT_UPDATE: - processGVCGUpdate(mac); + case PHASE1_4A_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE: + processAnswer(message, mac); break; - case PHASE1_70_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED: - processUTUVCGUAbbreviated(mac); + case PHASE1_4C_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED: + processRadioUnitMonitor(message, mac); break; - case PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: - processGVCGUExplicit(mac); + case PHASE1_52_SNDCP_DATA_CHANNEL_REQUEST: + //Ignore - this is an inbound request by the SU break; - case PHASE1_198_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED: - processUTUVCGUExtended(mac); + case PHASE1_53_SNDCP_DATA_PAGE_RESPONSE: + //Ignore - this is an inbound request by the SU break; - case TDMA_17_INDIRECT_GROUP_PAGING: - broadcast(message, mac, getCurrentChannel(), DecodeEventType.PAGE, "GROUP PAGE"); + case PHASE1_54_SNDCP_DATA_CHANNEL_GRANT: + processChannelGrant(message, mac); break; - case TDMA_18_INDIVIDUAL_PAGING_MESSAGE_WITH_PRIORITY: - processIPMWP(message, mac); + case PHASE1_55_SNDCP_DATA_PAGE_REQUEST: + processDataPageRequest(message, mac); break; - case TDMA_48_POWER_CONTROL_SIGNAL_QUALITY: - processPCSQ(message, mac); + case PHASE1_58_STATUS_UPDATE_ABBREVIATED: + processStatus(message, mac); break; - case TDMA_49_MAC_RELEASE: - processMacRelease(message, mac); + case PHASE1_5A_STATUS_QUERY_ABBREVIATED: + processStatus(message, mac); break; - case PHASE1_65_GROUP_VOICE_SERVICE_REQUEST: - if(mac instanceof GroupVoiceServiceRequest gvsr) - { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, "GROUP VOICE SERVICE " + gvsr.getServiceOptions()); - } + case PHASE1_5C_MESSAGE_UPDATE_ABBREVIATED: + processMessageUpdate(message, mac); break; - case PHASE1_68_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_ABBREVIATED: - if(mac instanceof UnitToUnitVoiceChannelGrantAbbreviated uuvcga) - { - broadcast(message, mac, uuvcga.getChannel(), DecodeEventType.CALL_UNIT_TO_UNIT, "VOICE CHANNEL GRANT"); - } + case PHASE1_5D_RADIO_UNIT_MONITOR_COMMAND_OBSOLETE: + case PHASE1_5E_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_ABBREVIATED: + processRadioUnitMonitor(message, mac); break; - case PHASE1_69_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED: - if(mac instanceof UnitToUnitAnswerRequestAbbreviated uuara) - { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, "UNIT-TO-UNIT ANSWER REQUEST - " + uuara.getServiceOptions()); - } + case PHASE1_5F_CALL_ALERT_ABBREVIATED: + processCallAlert(message, mac); break; - case PHASE1_74_TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - if(mac instanceof TelephoneInterconnectAnswerRequest tiar) - { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, "TELEPHONE INTERCONNECT ANSWER REQUEST"); - } + case PHASE1_60_ACKNOWLEDGE_RESPONSE_FNE_ABBREVIATED: + processAcknowledge(message, mac); break; - case PHASE1_76_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED: - processRUMCA(message, mac); + case PHASE1_61_QUEUED_RESPONSE: + processQueued(message, mac); break; - case PHASE1_84_SNDCP_DATA_CHANNEL_GRANT: - processSDCG(message, mac); + case PHASE1_64_EXTENDED_FUNCTION_COMMAND_ABBREVIATED: + processExtendedFunctionCommand(message, mac); break; - case PHASE1_85_SNDCP_DATA_PAGE_REQUEST: - if(mac instanceof SNDCPDataPageRequest sdpr) - { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.PAGE, "SNDCP DATA PAGE " + sdpr.getServiceOptions()); - } + case PHASE1_67_DENY_RESPONSE: + processDeny(message, mac); break; - case PHASE1_88_STATUS_UPDATE_ABBREVIATED: - processSUA(message, mac); + case PHASE1_68_GROUP_AFFILIATION_RESPONSE_ABBREVIATED: + case PHASE1_6A_GROUP_AFFILIATION_QUERY_ABBREVIATED: + processAffiliation(message, mac); break; - case PHASE1_90_STATUS_QUERY_ABBREVIATED: - if(mac instanceof StatusQueryAbbreviated) - { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.STATUS, "STATUS QUERY"); - } + case PHASE1_6B_LOCATION_REGISTRATION_RESPONSE: + processLocationRegistration(message, mac); break; - case PHASE1_92_MESSAGE_UPDATE_ABBREVIATED: - if(mac instanceof MessageUpdateAbbreviated mua) + case PHASE1_6C_UNIT_REGISTRATION_RESPONSE_ABBREVIATED: + case PHASE1_6D_UNIT_REGISTRATION_COMMAND_ABBREVIATED: + case PHASE1_6F_DEREGISTRATION_ACKNOWLEDGE: + processUnitRegistration(message, mac); + break; + case PHASE1_70_SYNCHRONIZATION_BROADCAST: + //Ignore - channel timing information + break; + case PHASE1_71_AUTHENTICATION_DEMAND: + case PHASE1_72_AUTHENTICATION_FNE_RESPONSE_ABBREVIATED: + processAuthentication(message, mac); + break; + case PHASE1_76_ROAMING_ADDRESS_COMMAND: + case PHASE1_77_ROAMING_ADDRESS_UPDATE: + processRoamingAddress(message, mac); + break; + case PHASE1_73_IDENTIFIER_UPDATE_TDMA_ABBREVIATED: + case PHASE1_74_IDENTIFIER_UPDATE_V_UHF: + processNetwork(message, mac); + break; + case PHASE1_75_TIME_AND_DATE_ANNOUNCEMENT: + //Ignore + break; + case PHASE1_78_SYSTEM_SERVICE_BROADCAST: + case PHASE1_79_SECONDARY_CONTROL_CHANNEL_BROADCAST_IMPLICIT: + case PHASE1_7A_RFSS_STATUS_BROADCAST_IMPLICIT: + case PHASE1_7B_NETWORK_STATUS_BROADCAST_IMPLICIT: + case PHASE1_7C_ADJACENT_STATUS_BROADCAST_IMPLICIT: + case PHASE1_7D_IDENTIFIER_UPDATE: + processNetwork(message, mac); + break; + + /** + * Partition 3 Opcodes + */ + case PHASE1_EXTENDED_PARTITION_3_UNKNOWN_OPCODE: + //Ignore + break; + case PHASE1_90_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED: + if(mac instanceof GroupRegroupVoiceChannelUserAbbreviated gr) { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.SDM, "MESSAGE UPDATE - " + mua.getShortDataMessage()); + mPatchGroupManager.addPatchGroup(gr.getPatchgroup(), message.getTimestamp()); } + processChannelUser(message, mac); + break; + case PHASE1_C0_GROUP_VOICE_CHANNEL_GRANT_EXPLICIT: + processChannelGrant(message, mac); + break; + case PHASE1_C3_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + processChannelGrantUpdate(message, mac); + break; + case PHASE1_C4_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_VCH: + processChannelGrant(message, mac); + break; + case PHASE1_C5_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED: + processAnswer(message, mac); + break; + case PHASE1_C6_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_VCH: + case PHASE1_C7_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_LCCH: + processChannelGrantUpdate(message, mac); + break; + case PHASE1_C8_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_EXPLICIT: + processChannelGrant(message, mac); break; - case PHASE1_94_RADIO_UNIT_MONITOR_COMMAND_ENHANCED: - processRUMCE(message, mac); + case PHASE1_C9_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + processChannelGrantUpdate(message, mac); break; - case PHASE1_95_CALL_ALERT_ABBREVIATED: + case PHASE1_CB_CALL_ALERT_EXTENDED_LCCH: processCallAlert(message, mac); break; - case PHASE1_96_ACK_RESPONSE: - if(mac instanceof AcknowledgeResponse ar) - { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.RESPONSE, "ACKNOWLEDGE: " + ar.getServiceType()); - } + case PHASE1_CC_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_VCH: + case PHASE1_CD_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_LCCH: + processRadioUnitMonitor(message, mac); break; - case PHASE1_97_QUEUED_RESPONSE: - processQueuedResponse(message, mac); + case PHASE1_CE_MESSAGE_UPDATE_EXTENDED_LCCH: + processMessageUpdate(message, mac); break; - case PHASE1_100_EXTENDED_FUNCTION_COMMAND_ABBREVIATED: - processEFCA(message, mac); + case PHASE1_CF_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_LCCH: + processChannelGrant(message, mac); break; - case PHASE1_103_DENY_RESPONSE: - processDenyResponse(message, mac); + case PHASE1_D6_SNDCP_DATA_CHANNEL_ANNOUNCEMENT: + processNetwork(message, mac); break; - case PHASE1_106_GROUP_AFFILIATION_QUERY_ABBREVIATED: - broadcast(message, mac, getCurrentChannel(), DecodeEventType.QUERY, "GROUP AFFILIATION"); + case PHASE1_D8_STATUS_UPDATE_EXTENDED_VCH: + case PHASE1_D9_STATUS_UPDATE_EXTENDED_LCCH: + case PHASE1_DA_STATUS_QUERY_EXTENDED_VCH: + case PHASE1_DB_STATUS_QUERY_EXTENDED_LCCH: + processStatus(message, mac); break; - case PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED: - broadcast(message, mac, getCurrentChannel(), DecodeEventType.COMMAND, "UNIT REGISTRATION"); + case PHASE1_DC_MESSAGE_UPDATE_EXTENDED_VCH: + processMessageUpdate(message, mac); break; - case PHASE1_128_L3HARRIS_GPS_LOCATION: - processL3HarrisGps(message, mac); + case PHASE1_DE_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_EXTENDED: + processRadioUnitMonitor(message, mac); break; - case PHASE1_168_L3HARRIS_TALKER_ALIAS: - if(mac instanceof L3HarrisTalkerAlias talkerAlias) - { - getIdentifierCollection().update(talkerAlias.getAlias()); - } + case PHASE1_DF_CALL_ALERT_EXTENDED_VCH: + processCallAlert(message, mac); + break; + case PHASE1_E0_ACKNOWLEDGE_RESPONSE_FNE_EXTENDED: + processAcknowledge(message, mac); + break; + case PHASE1_E4_EXTENDED_FUNCTION_COMMAND_EXTENDED_VCH: + case PHASE1_E5_EXTENDED_FUNCTION_COMMAND_EXTENDED_LCCH: + processExtendedFunctionCommand(message, mac); + break; + case PHASE1_E8_GROUP_AFFILIATION_RESPONSE_EXTENDED: + processAffiliation(message, mac); break; - case PHASE1_176_L3HARRIS_GROUP_REGROUP: - if(mac instanceof L3HarrisRegroupCommand regroup) + case PHASE1_E9_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + processNetwork(message, mac); + break; + case PHASE1_EA_GROUP_AFFILIATION_QUERY_EXTENDED: + processAffiliation(message, mac); + break; + case PHASE1_EC_UNIT_REGISTRATION_RESPONSE_EXTENDED: + processUnitRegistration(message, mac); + break; + case PHASE1_F2_AUTHENTICATION_FNE_RESPONSE_EXTENDED: + processAuthentication(message, mac); + break; + case PHASE1_F3_IDENTIFIER_UPDATE_TDMA_EXTENDED: + case PHASE1_FA_RFSS_STATUS_BROADCAST_EXPLICIT: + case PHASE1_FB_NETWORK_STATUS_BROADCAST_EXPLICIT: + case PHASE1_FC_ADJACENT_STATUS_BROADCAST_EXPLICIT: + case PHASE1_FE_ADJACENT_STATUS_BROADCAST_EXTENDED_EXPLICIT: + processNetwork(message, mac); + break; + + /** + * Partition 2 Opcodes + */ + case VENDOR_PARTITION_2_UNKNOWN_OPCODE: + //Ignore + break; + + //Vendor: L3Harris + case L3HARRIS_A0_PRIVATE_DATA_CHANNEL_GRANT: + case L3HARRIS_AC_UNIT_TO_UNIT_DATA_CHANNEL_GRANT: + processChannelGrant(message, mac); + break; + case L3HARRIS_A8_TALKER_ALIAS: + processTalkerAlias(message, mac); + break; + case L3HARRIS_AA_GPS_LOCATION: + processGPS(message, mac); + break; + case L3HARRIS_B0_GROUP_REGROUP_EXPLICIT_ENCRYPTION_COMMAND: + processDynamicRegrouping(message, mac); + break; + + //Vendor: Motorola + case MOTOROLA_80_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED: + if(mac instanceof MotorolaGroupRegroupVoiceChannelUserAbbreviated gr) { - if(regroup.getRegroupOptions().isActivate()) - { - mPatchGroupManager.addPatchGroup(regroup.getPatchGroup()); - } - else - { - mPatchGroupManager.removePatchGroup(regroup.getPatchGroup()); - } + mPatchGroupManager.addPatchGroup(gr.getPatchGroup(), message.getTimestamp()); } + processChannelUser(message, mac); break; - case PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED: - if(mac instanceof UnitToUnitAnswerRequestExtended uuare) + case MOTOROLA_81_GROUP_REGROUP_ADD: + processDynamicRegrouping(message, mac); + break; + case MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE: + if(mac instanceof MotorolaGroupRegroupVoiceChannelUpdate gr) { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, "UNIT-TO-UNIT ANSWER REQUEST " + uuare.getServiceOptions()); + mPatchGroupManager.addPatchGroup(gr.getPatchgroup(), message.getTimestamp()); } + processChannelGrantUpdate(message, mac); break; - case PHASE1_204_RADIO_UNIT_MONITOR_COMMAND_EXTENDED: - processRUMCExtended(message, mac); + case MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND: + processExtendedFunctionCommand(message, mac); break; - case PHASE1_216_STATUS_UPDATE_EXTENDED: - processSUE(message, mac); + case MOTOROLA_89_GROUP_REGROUP_DELETE: + processDynamicRegrouping(message, mac); break; - case PHASE1_218_STATUS_QUERY_EXTENDED: - broadcast(message, mac, getCurrentChannel(), DecodeEventType.STATUS, "STATUS QUERY"); + case MOTOROLA_91_GROUP_REGROUP_UNKNOWN: + //Unknown break; - case PHASE1_220_MESSAGE_UPDATE_EXTENDED: - if(mac instanceof MessageUpdateExtended mue) + case MOTOROLA_95_UNKNOWN_149: + //Unknown + break; + case MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED: + if(mac instanceof MotorolaGroupRegroupVoiceChannelUserExtended gr) { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.SDM, "MESSAGE UPDATE - " + mue.getShortDataMessage()); + mPatchGroupManager.addPatchGroup(gr.getPatchgroup(), message.getTimestamp()); } + processChannelUser(message, mac); break; - case PHASE1_223_CALL_ALERT_EXTENDED: - if(mac instanceof CallAlertExtended) + case MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT: + if(mac instanceof MotorolaGroupRegroupChannelGrantImplicit gr) { - processCallAlert(message, mac); + mPatchGroupManager.addPatchGroup(gr.getTargetAddress(), message.getTimestamp()); } + processChannelGrant(message, mac); break; - case PHASE1_228_EXTENDED_FUNCTION_COMMAND_EXTENDED: - processEFCE(message, mac); - break; - case PHASE1_234_GROUP_AFFILIATION_QUERY_EXTENDED: - if(mac instanceof GroupAffiliationQueryExtended) + case MOTOROLA_A4_GROUP_REGROUP_CHANNEL_GRANT_EXPLICIT: + if(mac instanceof MotorolaGroupRegroupChannelGrantExplicit gr) { - broadcast(message, mac, getCurrentChannel(), DecodeEventType.QUERY, "GROUP AFFILIATION"); + mPatchGroupManager.addPatchGroup(gr.getTargetAddress(), message.getTimestamp()); } + processChannelGrant(message, mac); break; + case MOTOROLA_A5_GROUP_REGROUP_CHANNEL_GRANT_UPDATE: + if(mac instanceof MotorolaGroupRegroupChannelGrantUpdate gr) + { + mPatchGroupManager.addPatchGroup(gr.getPatchgroupA(), message.getTimestamp()); - case PHASE1_115_IDENTIFIER_UPDATE_TDMA: - case PHASE1_116_IDENTIFIER_UPDATE_V_UHF: - case PHASE1_117_TIME_AND_DATE_ANNOUNCEMENT: - case PHASE1_120_SYSTEM_SERVICE_BROADCAST: - case PHASE1_121_SECONDARY_CONTROL_CHANNEL_BROADCAST_ABBREVIATED: - case PHASE1_122_RFSS_STATUS_BROADCAST_ABBREVIATED: - case PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED: - case PHASE1_124_ADJACENT_STATUS_BROADCAST_ABBREVIATED: - case PHASE1_125_IDENTIFIER_UPDATE: - case PHASE1_PARTITION_1_UNKNOWN_OPCODE: - case VENDOR_PARTITION_2_UNKNOWN_OPCODE: - case PHASE1_214_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: - case PHASE1_233_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - case PHASE1_250_RFSS_STATUS_BROADCAST_EXTENDED: - case PHASE1_251_NETWORK_STATUS_BROADCAST_EXTENDED: - case PHASE1_252_ADJACENT_STATUS_BROADCAST_EXTENDED: - case PHASE1_EXTENDED_PARTITION_3_UNKNOWN_OPCODE: - case TDMA_PARTITION_0_UNKNOWN_OPCODE: - case OBSOLETE_PHASE1_93_RADIO_UNIT_MONITOR_COMMAND: - case PHASE1_192_GROUP_VOICE_CHANNEL_GRANT_EXTENDED: - case PHASE1_196_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_EXTENDED: + if(gr.hasPatchgroupB()) + { + mPatchGroupManager.addPatchGroup(gr.getPatchgroupB(), message.getTimestamp()); + } + } + processChannelGrantUpdate(message, mac); + break; + case MOTOROLA_A6_QUEUED_RESPONSE: + processQueued(message, mac); + break; + case MOTOROLA_A7_DENY_RESPONSE: + processDeny(message, mac); + break; + case MOTOROLA_A8_ACKNOWLEDGE_RESPONSE: + processAcknowledge(message, mac); + break; + case L3HARRIS_81_UNKNOWN_OPCODE_129: + //Unknown + break; + case PHASE1_88_UNKNOWN_LCCH_OPCODE: + //Unknown + break; + case L3HARRIS_8F_UNKNOWN_OPCODE_143: + //Unknown + break; case UNKNOWN: default: break; } } - private void processQueuedResponse(MacMessage message, MacStructure mac) { - if(mac instanceof QueuedResponse qr) - { - broadcast(message, mac, DecodeEventType.RESPONSE, - "QUEUED - " + qr.getQueuedResponseServiceType() + - " REASON:" + qr.getQueuedResponseReason() + " ADDL:" + qr.getAdditionalInfo()); - } - } - - - private void processEFCA(MacMessage message, MacStructure mac) { - if(mac instanceof ExtendedFunctionCommand efc) - { - broadcast(message, mac, DecodeEventType.COMMAND, - "EXTENDED FUNCTION: " + efc.getExtendedFunction() + " ARGUMENTS:" + efc.getArguments()); - } + /** + * Creates a copy of the current identifier collection with all USER and CHANNEL identifiers removed and adds the + * user identifier argument added to the collection. + * @param userIdentifierToAdd to add to the collection + * @param timestamp to check for freshness of patch groups. + */ + private MutableIdentifierCollection getIdentifierCollectionForUser(Identifier userIdentifierToAdd, long timestamp) + { + return getIdentifierCollectionForUsers(Collections.singletonList(userIdentifierToAdd), timestamp); } - private void processDenyResponse(MacMessage message, MacStructure mac) { - if(mac instanceof DenyResponse dr) + /** + * Creates a copy of the current identifier collection with all USER and CHANNEL identifiers removed and adds the + * user identifiers argument added to the collection. + * @param identifiersToAdd to add to the collection + * @param timestamp to check for freshness of patch groups. + */ + private MutableIdentifierCollection getIdentifierCollectionForUsers(List identifiersToAdd, long timestamp) + { + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.remove(Form.CHANNEL); + for(Identifier identifier : identifiersToAdd) { - broadcast(message, mac, DecodeEventType.RESPONSE, - "DENY: " + dr.getDeniedServiceType() + " REASON:" + dr.getDenyReason() + " ADDL:" + dr.getAdditionalInfo()); + //Filter the identifiers through the patch group manager + ic.update(mPatchGroupManager.update(identifier, timestamp)); } + return ic; } - private void processRUMCExtended(MacMessage message, MacStructure mac) { - if(mac instanceof RadioUnitMonitorCommandExtended rumce) + /** + * Acknowledgements. + */ + private void processAcknowledge(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) { - broadcast(message, mac, DecodeEventType.COMMAND, - "RADIO UNIT MONITOR" + (rumce.isSilentMonitor() ? " (STEALTH)" : "") + - " TIME:" + rumce.getTransmitTime() + "MULTIPLIER:" + rumce.getTransmitMultiplier()); - } - } + case PHASE1_60_ACKNOWLEDGE_RESPONSE_FNE_ABBREVIATED: + if(mac instanceof AcknowledgeResponseFNEAbbreviated ar) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.ACKNOWLEDGE, "ACKNOWLEDGE: " + ar.getServiceType()); + } + break; + case PHASE1_E0_ACKNOWLEDGE_RESPONSE_FNE_EXTENDED: + if(mac instanceof AcknowledgeResponseFNEExtended ar) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.ACKNOWLEDGE, "ACKNOWLEDGE: " + ar.getServiceType()); + } + break; + case MOTOROLA_A8_ACKNOWLEDGE_RESPONSE: + if(mac instanceof MotorolaAcknowledgeResponse ar) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.ACKNOWLEDGE, "ACKNOWLEDGE: " + ar.getServiceType()); + } + break; - private void processSUE(MacMessage message, MacStructure mac) { - if(mac instanceof StatusUpdateExtended sue) - { - broadcast(message, mac, DecodeEventType.STATUS, - "STATUS UPDATE - UNIT:" + sue.getUnitStatus() + " USER:" + sue.getUserStatus()); } } - private void processEFCE(MacMessage message, MacStructure mac) { - if(mac instanceof ExtendedFunctionCommandExtended efce) + /** + * Affiliation request and response (for talkgroups). + */ + private void processAffiliation(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) { - broadcast(message, mac, DecodeEventType.COMMAND, - "EXTENDED FUNCTION: " + efce.getExtendedFunction() + " ARGUMENTS:" + efce.getArguments()); + case PHASE1_68_GROUP_AFFILIATION_RESPONSE_ABBREVIATED: + case PHASE1_E8_GROUP_AFFILIATION_RESPONSE_EXTENDED: + if(mac instanceof GroupAffiliationResponseAbbreviated || mac instanceof GroupAffiliationResponseExtended) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.RESPONSE, "GROUP AFFILIATION"); + } + break; + case PHASE1_6A_GROUP_AFFILIATION_QUERY_ABBREVIATED: + case PHASE1_EA_GROUP_AFFILIATION_QUERY_EXTENDED: + if(mac instanceof GroupAffiliationQueryAbbreviated || mac instanceof GroupAffiliationQueryExtended) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.QUERY, "GROUP AFFILIATION"); + } + break; } } - private void processCallAlert(MacMessage message, MacStructure mac) { - broadcast(message, mac, DecodeEventType.CALL_ALERT, null); - } - - private void processRUMCE(MacMessage message, MacStructure mac) { - if(mac instanceof RadioUnitMonitorCommandEnhanced rumc) + /** + * Answer Request & Response + */ + private void processAnswer(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) { - broadcast(message, mac, DecodeEventType.COMMAND, - "RADIO UNIT MONITOR" + (rumc.isStealthMode() ? " (STEALTH)" : "") + - " ENCRYPTION:" + rumc.getEncryption() + - " TIME:" + rumc.getTransmitTime()); + case PHASE1_45_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED: + if(mac instanceof UnitToUnitAnswerRequestAbbreviated uuara) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, + "UNIT-TO-UNIT ANSWER REQUEST - " + uuara.getServiceOptions()); + } + break; + case PHASE1_4A_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE: + if(mac instanceof TelephoneInterconnectAnswerRequest tiar) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, + "TELEPHONE INTERCONNECT ANSWER REQUEST"); + } + break; + case PHASE1_C5_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED: + if(mac instanceof UnitToUnitAnswerRequestExtended uuare) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.REQUEST, "UNIT-TO-UNIT ANSWER REQUEST " + + uuare.getServiceOptions()); + } + break; } } - private void processSUA(MacMessage message, MacStructure mac) { - if(mac instanceof StatusUpdateAbbreviated sua) + /** + * Authentication + */ + private void processAuthentication(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) { - broadcast(message, mac, DecodeEventType.STATUS, - "STATUS UPDATE - UNIT:" + sua.getUnitStatus() + " USER:" + sua.getUserStatus()); + case PHASE1_71_AUTHENTICATION_DEMAND: + if(mac instanceof AuthenticationDemand ad) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.COMMAND, + "AUTHENTICATE - SEED:" + ad.getRandomSeed() + " CHALLENGE:" + ad.getChallenge()); + } + break; + case PHASE1_72_AUTHENTICATION_FNE_RESPONSE_ABBREVIATED: + if(mac instanceof AuthenticationFNEResponseAbbreviated ar) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.RESPONSE, + "AUTHENTICATION " + ar.getResponse()); + } + break; + case PHASE1_F2_AUTHENTICATION_FNE_RESPONSE_EXTENDED: + if(mac instanceof AuthenticationFNEResponseExtended ar) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.RESPONSE, + "AUTHENTICATION " + ar.getResponse()); + } + break; } } - private void processSDCG(MacMessage message, MacStructure mac) { - if(mac instanceof SNDCPDataChannelGrant sdcg) - { - broadcast(message, mac, - sdcg.getServiceOptions().isEncrypted() ? DecodeEventType.DATA_CALL_ENCRYPTED : DecodeEventType.DATA_CALL, - "SNDCP CHANNEL GRANT " + sdcg.getServiceOptions()); - } + /** + * Call Alert for the target radio to call the source radio + */ + private void processCallAlert(MacMessage message, MacStructure mac) + { + broadcast(message, mac, DecodeEventType.CALL_ALERT, "CALL THE FROM RADIO"); } - private void processRUMCA(MacMessage message, MacStructure mac) { - if(mac instanceof RadioUnitMonitorCommand rumc) + /** + * Channel grants + */ + private void processChannelGrant(MacMessage message, MacStructure mac) + { + if(message.getMacPduType() == MacPduType.MAC_3_IDLE || message.getMacPduType() == MacPduType.MAC_6_HANGTIME) { - broadcast(message, mac, DecodeEventType.COMMAND, - "RADIO UNIT MONITOR" + (rumc.isSilentMonitor() ? " (STEALTH)" : "") + - " TIME:" + rumc.getTransmitTime() + " MULTIPLIER:" + rumc.getTransmitMultiplier()); - } - } + for(Identifier identifier : mac.getIdentifiers()) + { + //Add to the identifier collection after filtering through the patch group manager + getIdentifierCollection().update(mPatchGroupManager.update(identifier, message.getTimestamp())); + } - private void processMacRelease(MacMessage message, MacStructure mac) { - if(mac instanceof MacRelease mr) - { - closeCurrentCallEvent(message.getTimestamp(), true, message.getMacPduType()); - broadcast(message, mac, DecodeEventType.COMMAND, - (mr.isForcedPreemption() ? "FORCED " : "") + "CALL PREEMPTION" + (mr.isTalkerPreemption() ? " BY USER" : "")); + continueState(State.ACTIVE); } - } - private void processPCSQ(MacMessage message, MacStructure mac) { - if(mac instanceof PowerControlSignalQuality pcsq) + //All channel grant messages implement this interface + if(mac instanceof IP25ChannelGrantDetailProvider cgdp) { - broadcast(message, mac, DecodeEventType.COMMAND, - "ADJUST TRANSMIT POWER - RF:" + pcsq.getRFLevel() + " BER:" + pcsq.getBitErrorRate()); - } - } + updateCurrentChannel(cgdp.getChannel()); - private void processIPMWP(MacMessage message, MacStructure mac) { - if(mac instanceof IndividualPagingMessage ipm) - { - boolean priority = ipm.isTalkgroupPriority1() || ipm.isTalkgroupPriority2() || - ipm.isTalkgroupPriority3() || ipm.isTalkgroupPriority4(); - broadcast(message, mac, DecodeEventType.PAGE, - (priority ? "PRIORITY " : "") + "USER PAGE"); + //Only dispatch channel grant if we're a control channel. + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection ic = getIdentifierCollectionForUsers(mac.getIdentifiers(), message.getTimestamp()); + //Add the traffic channel to the IC + ic.update(cgdp.getChannel()); + mTrafficChannelManager.processP2ChannelGrant(cgdp.getChannel(), cgdp.getServiceOptions(), ic, mac.getOpcode(), + message.getTimestamp()); + } } } /** - * Broadcasts the L3Harris gps position report as a mappable/plottable event. + * Channel Grant Update. Indicates to the current channel users that there is activity on other channels involving + * talkgroups that one or more of the current call radios may want to join and the message contains enough info + * for the radio to move directly to the channel to take part in the call. */ - private void processL3HarrisGps(MacMessage message, MacStructure structure) + private void processChannelGrantUpdate(MacMessage message, MacStructure mac) { - if(structure instanceof L3HarrisGpsLocation gps) + if(message.getMacPduType() == MacPduType.MAC_3_IDLE || message.getMacPduType() == MacPduType.MAC_6_HANGTIME) { - MutableIdentifierCollection collection = getUpdatedMutableIdentifierCollection(gps); - - broadcast(PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, message.getTimestamp()) - .protocol(Protocol.APCO25) - .location(gps.getGeoPosition()) - .channel(getCurrentChannel()) - .details(gps.getLocation().toString() + " " + new Date(gps.getTimestampMs())) - .identifiers(collection) - .build()); + continueState(State.ACTIVE); } - } - private void broadcast(MacMessage message, MacStructure mac, IChannelDescriptor currentChannel, DecodeEventType eventType, String details) { - MutableIdentifierCollection collection = getUpdatedMutableIdentifierCollection(mac); + switch(mac.getOpcode()) + { + case TDMA_05_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_IMPLICIT: + if(mac instanceof GroupVoiceChannelGrantUpdateMultipleImplicit cgu) + { + updateCurrentChannel(cgu.getChannel1()); - broadcast(P25DecodeEvent.builder(eventType, message.getTimestamp()) - .channel(currentChannel) - .details(details) - .identifiers(collection) - .build()); - } + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getGroupAddress1(), message.getTimestamp()); + mic.update(cgu.getChannel1()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions1(), mic, + mac.getOpcode(), message.getTimestamp()); + } - private void broadcast(MacMessage message, MacStructure structure, DecodeEventType eventType, String details) { - MutableIdentifierCollection icQueuedResponse = getUpdatedMutableIdentifierCollection(structure); + if(cgu.hasGroup2()) + { + updateCurrentChannel(cgu.getChannel2()); - broadcast(P25DecodeEvent.builder(eventType, message.getTimestamp()) - .channel(getCurrentChannel()) - .details(details) - .identifiers(icQueuedResponse) - .build()); - } + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic2 = getIdentifierCollectionForUser(cgu.getGroupAddress2(), message.getTimestamp()); + mic2.update(cgu.getChannel1()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions2(), mic2, + mac.getOpcode(), message.getTimestamp()); + } + } - private MutableIdentifierCollection getUpdatedMutableIdentifierCollection(MacStructure mac) { - MutableIdentifierCollection icQueuedResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icQueuedResponse.remove(IdentifierClass.USER); - icQueuedResponse.update(mac.getIdentifiers()); - return icQueuedResponse; - } + if(cgu.hasGroup3()) + { + updateCurrentChannel(cgu.getChannel3()); - private void processUTUVCGUExtended(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantUpdateExtended uuvcgue) - { - if(isCurrentGroup(uuvcgue.getTargetAddress())) - { - broadcastCurrentChannel(uuvcgue.getChannel()); - } + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic3 = getIdentifierCollectionForUser(cgu.getGroupAddress3(), message.getTimestamp()); + mic3.update(cgu.getChannel1()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions3(), mic3, + mac.getOpcode(), message.getTimestamp()); + } + } + } + break; + case TDMA_25_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT: + if(mac instanceof GroupVoiceChannelGrantUpdateMultipleExplicit cgu) + { + updateCurrentChannel(cgu.getChannel1()); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getGroupAddress1(), message.getTimestamp()); + mic.update(cgu.getChannel1()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions1(), mic, + mac.getOpcode(), message.getTimestamp()); + } + + if(cgu.hasGroup2()) + { + updateCurrentChannel(cgu.getChannel2()); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic2 = getIdentifierCollectionForUser(cgu.getGroupAddress2(), message.getTimestamp()); + mic2.update(cgu.getChannel1()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions2(), mic2, + mac.getOpcode(), message.getTimestamp()); + } + } + } + break; + case PHASE1_42_GROUP_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: + if(mac instanceof GroupVoiceChannelGrantUpdateImplicit cgu) + { + updateCurrentChannel(cgu.getChannel1()); + + //Create an empty service options + VoiceServiceOptions serviceOptions = new VoiceServiceOptions(0); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getGroupAddress1(), message.getTimestamp()); + mic.update(cgu.getChannel1()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), serviceOptions, mic, mac.getOpcode(), + message.getTimestamp()); + } + + if(cgu.hasGroup2()) + { + updateCurrentChannel(cgu.getChannel2()); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic2 = getIdentifierCollectionForUser(cgu.getGroupAddress2(), message.getTimestamp()); + mic2.update(cgu.getChannel1()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), serviceOptions, mic2, + mac.getOpcode(), message.getTimestamp()); + } + } + } + break; + case PHASE1_46_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED: + if(mac instanceof UnitToUnitVoiceChannelGrantUpdateAbbreviated cgu) + { + updateCurrentChannel(cgu.getChannel()); + + if(mChannel.isStandardChannel()) + { + //Create an empty service options + VoiceServiceOptions serviceOptions = new VoiceServiceOptions(0); + MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); + mic.update(cgu.getChannel()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), serviceOptions, mic, mac.getOpcode(), + message.getTimestamp()); + } + } + break; + case PHASE1_49_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: + if(mac instanceof TelephoneInterconnectVoiceChannelGrantUpdateImplicit cgu) + { + updateCurrentChannel(cgu.getChannel()); + + if(mChannel.isStandardChannel()) + { + //Create an empty service options + VoiceServiceOptions serviceOptions = new VoiceServiceOptions(0); + MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); + mic.update(cgu.getChannel()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), serviceOptions, mic, mac.getOpcode(), + message.getTimestamp()); + } + } + break; + case PHASE1_C3_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + if(mac instanceof GroupVoiceChannelGrantUpdateExplicit cgu) + { + updateCurrentChannel(cgu.getChannel()); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getGroupAddress(), message.getTimestamp()); + mic.update(cgu.getChannel()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), cgu.getServiceOptions(), mic, + mac.getOpcode(), message.getTimestamp()); + } + } + break; + case PHASE1_C6_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_VCH: + if(mac instanceof UnitToUnitVoiceChannelGrantUpdateExtendedVCH cgu) + { + updateCurrentChannel(cgu.getChannel()); + + if(mChannel.isStandardChannel()) + { + //Create an empty service options + VoiceServiceOptions serviceOptions = new VoiceServiceOptions(0); + MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); + mic.update(cgu.getChannel()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), serviceOptions, mic, mac.getOpcode(), + message.getTimestamp()); + } + } + break; + case PHASE1_C7_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_LCCH: + if(mac instanceof UnitToUnitVoiceChannelGrantUpdateExtendedLCCH cgu) + { + updateCurrentChannel(cgu.getChannel()); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); + mic.update(cgu.getChannel()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), cgu.getServiceOptions(), mic, + mac.getOpcode(), message.getTimestamp()); + } + } + break; + case PHASE1_C9_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + if(mac instanceof TelephoneInterconnectVoiceChannelGrantUpdateExplicit cgu) + { + updateCurrentChannel(cgu.getChannel()); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); + mic.update(cgu.getChannel()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), cgu.getServiceOptions(), mic, + mac.getOpcode(), message.getTimestamp()); + } + } + break; + case MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE: + if(mac instanceof MotorolaGroupRegroupVoiceChannelUpdate cgu) + { + mPatchGroupManager.addPatchGroup(cgu.getPatchgroup(), message.getTimestamp()); + updateCurrentChannel(cgu.getChannel()); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getPatchgroup(), message.getTimestamp()); + mic.update(cgu.getChannel()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), cgu.getServiceOptions(), mic, + mac.getOpcode(), message.getTimestamp()); + } + } + break; + case MOTOROLA_A5_GROUP_REGROUP_CHANNEL_GRANT_UPDATE: + if(mac instanceof MotorolaGroupRegroupChannelGrantUpdate cgu) + { + mPatchGroupManager.addPatchGroup(cgu.getPatchgroupA(), message.getTimestamp()); + updateCurrentChannel(cgu.getChannelA()); + + //Create an empty service options + VoiceServiceOptions serviceOptions = new VoiceServiceOptions(0); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getPatchgroupA(), message.getTimestamp()); + mic.update(cgu.getChannelA()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannelA(), serviceOptions, mic, mac.getOpcode(), + message.getTimestamp()); + } + + if(cgu.hasPatchgroupB()) + { + mPatchGroupManager.addPatchGroup(cgu.getPatchgroupB(), message.getTimestamp()); + updateCurrentChannel(cgu.getChannelB()); + + if(mChannel.isStandardChannel()) + { + MutableIdentifierCollection mic2 = getIdentifierCollectionForUser(cgu.getPatchgroupB(), message.getTimestamp()); + mic2.update(cgu.getChannelB()); + mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannelB(), serviceOptions, mic2, + mac.getOpcode(), message.getTimestamp()); + } + } + } + default: + //Log unhandled opcodes at least once to detect and add support. + LOGGING_SUPPRESSOR.error(mac.getOpcode().name(), 1, + "Unrecognized Channel Grant Update Opcode: " + mac.getOpcode().name()); + break; } } - private void processGVCGUExplicit(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateExplicit gvcgue) + /** + * Updates the current channel from channel grant and update messaging. + * @param channelDescriptor to compare to the current frequency. + */ + private void updateCurrentChannel(IChannelDescriptor channelDescriptor) + { + if(getCurrentChannel() == null && + getCurrentFrequency() > 0 && + channelDescriptor.getDownlinkFrequency() == getCurrentFrequency()) { - if(isCurrentGroup(gvcgue.getGroupAddress())) + if(channelDescriptor instanceof APCO25Channel p25) { - broadcastCurrentChannel(gvcgue.getChannel()); + if(p25.getTimeslot() == getTimeslot()) + { + setCurrentChannel(channelDescriptor); + } + else if(getTimeslot() == P25P1Message.TIMESLOT_1) + { + APCO25Channel timeslot1 = APCO25Channel.create(p25.getValue().getDownlinkBandIdentifier(), + p25.getValue().getDownlinkChannelNumber() - 1); + timeslot1.setFrequencyBand(p25.getValue().getFrequencyBand()); + setCurrentChannel(timeslot1); + } + else + { + APCO25Channel timeslot2 = APCO25Channel.create(p25.getValue().getDownlinkBandIdentifier(), + p25.getValue().getDownlinkChannelNumber() + 1); + timeslot2.setFrequencyBand(p25.getValue().getFrequencyBand()); + setCurrentChannel(timeslot2); + } } } } - private void processUTUVCGUAbbreviated(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantAbbreviated uuvcga) + /** + * Channel user (ie current user on this channel). + * + * Note: calling code should ensure that the mac structure argument implements the IServiceOptionsProvider interface. + */ + private void processChannelUser(MacMessage message, MacStructure mac) + { + if(message.getMacPduType() == MacPduType.MAC_3_IDLE || message.getMacPduType() == MacPduType.MAC_6_HANGTIME) { - if(isCurrentGroup(uuvcga.getSourceAddress()) || isCurrentGroup(uuvcga.getTargetAddress())) + for(Identifier identifier : mac.getIdentifiers()) { - broadcastCurrentChannel(uuvcga.getChannel()); + //Add to the identifier collection after filtering through the patch group manager + getIdentifierCollection().update(mPatchGroupManager.update(identifier, message.getTimestamp())); } - } - } - private void processGVCGUpdate(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdate gvcgu) + continueState(State.ACTIVE); + } + else { - if(isCurrentGroup(gvcgu.getGroupAddressA())) + for(Identifier identifier : mac.getIdentifiers()) { - broadcastCurrentChannel(gvcgu.getChannelA()); + //Add to the identifier collection after filtering through the patch group manager + getIdentifierCollection().update(mPatchGroupManager.update(identifier, message.getTimestamp())); } - if(getCurrentChannel() == null && isCurrentGroup(gvcgu.getGroupAddressB())) + if(mac instanceof IServiceOptionsProvider sop) { - broadcastCurrentChannel(gvcgu.getChannelB()); + IChannelDescriptor currentChannel = mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), getCurrentChannel(), sop.getServiceOptions(), mac.getOpcode(), getIdentifierCollection().copyOf(), message.getTimestamp(), null); + + if(getCurrentChannel() == null) + { + setCurrentChannel(currentChannel); + } + + if(sop.getServiceOptions().isEncrypted()) + { + continueState(State.ENCRYPTED); + } + else + { + continueState(State.CALL); + } + } + else + { + LOGGING_SUPPRESSOR.error("Unrecognized Service Options Not Implemented:" + mac.getClass().getName(), + 1, "Unrecognized voice channel user MAC message. Please notify the developer " + + "that this class should implement the IServiceOptionsProvider interface. Class: " + + mac.getClass()); + continueState(State.ACTIVE); } } } - private void processGVCGAbbreviated(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantAbbreviated gvcga) + /** + * Push-To-Talk (ie start of audio on this channel) + */ + private void processPushToTalk(MacMessage message, MacStructure mac) + { + for(Identifier identifier : mac.getIdentifiers()) { - if(isCurrentGroup(gvcga.getGroupAddress())) - { - broadcastCurrentChannel(gvcga.getChannel()); - } + //Add to the identifier collection after filtering through the patch group manager + getIdentifierCollection().update(mPatchGroupManager.update(identifier, message.getTimestamp())); } - } - private void processGVCGUMExplicit(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultipleExplicit gvcgume) + if(mac instanceof PushToTalk ptt) { - if(isCurrentGroup(gvcgume.getGroupAddressA())) - { - broadcastCurrentChannel(gvcgume.getChannelA()); - } + VoiceServiceOptions vso = ptt.isEncrypted() ? VoiceServiceOptions.createEncrypted() : VoiceServiceOptions.createUnencrypted(); - if(getCurrentChannel() == null && isCurrentGroup(gvcgume.getGroupAddressB())) - { - broadcastCurrentChannel(gvcgume.getChannelB()); - } + mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), getCurrentChannel(), vso, mac.getOpcode(), getIdentifierCollection().copyOf(), message.getTimestamp(), ptt.isEncrypted() ? ptt.getEncryptionKey().toString() : null); + + broadcast(new DecoderStateEvent(this, Event.START, ptt.isEncrypted() ? State.ENCRYPTED : State.CALL, getTimeslot())); } } - private void processGVCGUM(MacStructure mac) { - if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultiple gvcgum) + /** + * Dynamic Regrouping + */ + private void processDynamicRegrouping(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) { - if(isCurrentGroup(gvcgum.getGroupAddressA())) - { - broadcastCurrentChannel(gvcgum.getChannelA()); - } + case L3HARRIS_B0_GROUP_REGROUP_EXPLICIT_ENCRYPTION_COMMAND: + if(mac instanceof L3HarrisGroupRegroupExplicitEncryptionCommand regroup) + { + if(regroup.getRegroupOptions().isActivate()) + { + if(mPatchGroupManager.addPatchGroup(regroup.getPatchGroup(), message.getTimestamp())) + { + broadcast(message, mac, DecodeEventType.DYNAMIC_REGROUP, "ACTIVATE " + regroup.getPatchGroup()); + } + } + else + { + if(mPatchGroupManager.removePatchGroup(regroup.getPatchGroup())) + { + broadcast(message, mac, DecodeEventType.DYNAMIC_REGROUP, "DEACTIVATE " + regroup.getPatchGroup()); + } + } + } + break; + case MOTOROLA_81_GROUP_REGROUP_ADD: + if(mac instanceof MotorolaGroupRegroupAddCommand mgrac) + { + if(mPatchGroupManager.addPatchGroup(mgrac.getPatchGroup(), message.getTimestamp())) + { + broadcast(message, mac, DecodeEventType.DYNAMIC_REGROUP, "ACTIVATE " + mgrac.getPatchGroup()); + } + } + break; + case MOTOROLA_89_GROUP_REGROUP_DELETE: + if(mac instanceof MotorolaGroupRegroupDeleteCommand mgrdc) + { + if(mPatchGroupManager.removePatchGroup(mgrdc.getPatchGroup())) + { + broadcast(message, mac, DecodeEventType.DYNAMIC_REGROUP, "DEACTIVATE " + mgrdc.getPatchGroup()); + } + } + break; + } + } - if(getCurrentChannel() == null && gvcgum.hasGroupB() && isCurrentGroup(gvcgum.getGroupAddressB())) - { - broadcastCurrentChannel(gvcgum.getChannelB()); - } + /** + * End Push-To-Talk (ie end of current audio segment on this channel). + */ + private void processEndPushToTalk(MacMessage message, MacStructure mac) + { + if(mac instanceof EndPushToTalk) + { + closeCurrentCallEvent(message.getTimestamp(), true, false); - if(getCurrentChannel() == null && gvcgum.hasGroupC() && isCurrentGroup(gvcgum.getGroupAddressC())) + if(message.getDataUnitID().isFACCH()) { - broadcastCurrentChannel(gvcgum.getChannelC()); + mEndPttOnFacchCounter++; + + //FNE sending 2 or more End PTT in FACCH timeslots indicates a traffic channel teardown event. + if(mEndPttOnFacchCounter > 1 && mChannel.isTrafficChannel()) + { + broadcast(new DecoderStateEvent(this, Event.END, State.TEARDOWN, getTimeslot())); + return; //don't issue a reset state after this. + } } + + continueState(State.RESET); } } - private void processTIVCU(MacMessage message, MacStructure mac) { - if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME) + /** + * Data Page Request. Indicates that trunked data service has been requested for the target radio. + */ + private void processDataPageRequest(MacMessage message, MacStructure mac) + { + if(mac instanceof SNDCPDataPageRequest sdpr) { - closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME); - - for(Identifier identifier : mac.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } + broadcast(message, mac, getCurrentChannel(), DecodeEventType.PAGE, "SNDCP DATA PAGE " + sdpr.getServiceOptions()); } - else + } + + + /** + * Deny Response + */ + private void processDeny(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) { - for(Identifier identifier : mac.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } + case PHASE1_67_DENY_RESPONSE: + if(mac instanceof DenyResponse dr) + { + broadcast(message, mac, DecodeEventType.RESPONSE, "DENY: " + dr.getDeniedServiceType() + " REASON:" + + dr.getDenyReason() + " ADDL:" + dr.getAdditionalInfo()); + } + break; + case MOTOROLA_A7_DENY_RESPONSE: + if(mac instanceof MotorolaDenyResponse dr) + { + broadcast(message, mac, DecodeEventType.RESPONSE, "DENY: " + dr.getDeniedServiceType() + " REASON:" + + dr.getDenyReason() + (dr.hasAdditionalInformation() ? " ADDL:" + dr.getAdditionalInfo() : "")); + } + break; + } + } - if(mac instanceof TelephoneInterconnectVoiceChannelUser tivcu) - { - if(tivcu.getServiceOptions().isEncrypted()) + /** + * Processes the Extended Function Command + */ + private void processExtendedFunctionCommand(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) + { + case PHASE1_64_EXTENDED_FUNCTION_COMMAND_ABBREVIATED: + if(mac instanceof ExtendedFunctionCommandAbbreviated efc) { - updateCurrentCall(DecodeEventType.CALL_INTERCONNECT_ENCRYPTED, null, message.getTimestamp()); + broadcast(message, mac, DecodeEventType.COMMAND, "EXTENDED FUNCTION: " + + efc.getExtendedFunction() + " ARGUMENTS:" + efc.getArguments()); } - else + break; + case PHASE1_E4_EXTENDED_FUNCTION_COMMAND_EXTENDED_VCH: + if(mac instanceof ExtendedFunctionCommandExtendedVCH efce) { - updateCurrentCall(DecodeEventType.CALL_INTERCONNECT, null, message.getTimestamp()); + broadcast(message, mac, DecodeEventType.COMMAND, "EXTENDED FUNCTION: " + + efce.getExtendedFunction() + " ARGUMENTS:" + efce.getArguments()); } - } + break; + case PHASE1_E5_EXTENDED_FUNCTION_COMMAND_EXTENDED_LCCH: + if(mac instanceof ExtendedFunctionCommandExtendedLCCH efce) + { + broadcast(message, mac, DecodeEventType.COMMAND, "EXTENDED FUNCTION: " + + efce.getExtendedFunction() + " ARGUMENTS:" + efce.getArguments()); + } + break; + case MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND: + if(mac instanceof MotorolaGroupRegroupExtendedFunctionCommand efc) + { + switch(efc.getExtendedFunction()) + { + case GROUP_REGROUP_CANCEL_SUPERGROUP: + broadcast(message, mac, DecodeEventType.COMMAND, "CANCEL SUPER GROUP FOR RADIO"); + break; + case GROUP_REGROUP_CREATE_SUPERGROUP: + broadcast(message, mac, DecodeEventType.COMMAND, "CREATE SUPER GROUP ADD RADIO" + + efc.getTargetAddress()); + } + } + break; + } } - private void processUTUVCUExtended(MacMessage message, MacStructure mac) { - if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME) + /** + * Identifier Update + */ + private void processNetwork(MacMessage message, MacStructure mac) + { + if(message.getMacPduType() == MacPduType.MAC_3_IDLE || message.getMacPduType() == MacPduType.MAC_6_HANGTIME) + { + continueState(State.ACTIVE); + } + + mNetworkConfigurationMonitor.processMacMessage(message); + + if(mac instanceof NetworkStatusBroadcastImplicit nsbi) + { + setCurrentChannel(nsbi.getChannel()); + } + else if(mac instanceof RfssStatusBroadcastImplicit rsbi) { - closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME); + setCurrentChannel(rsbi.getChannel()); + } - for(Identifier identifier : mac.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } + //Send the frequency bands to the traffic channel manager to use for traffic channel preload data + if(mac instanceof IFrequencyBand frequencyBand) + { + mTrafficChannelManager.processFrequencyBand(frequencyBand); } - else + } + + /** + * Roaming Address + */ + private void processRoamingAddress(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) { - for(Identifier identifier : mac.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } + case PHASE1_76_ROAMING_ADDRESS_COMMAND: + if(mac instanceof RoamingAddressCommand rac) + { + broadcast(message, mac, DecodeEventType.COMMAND, rac.getStackOperation() + " ROAMING ADDRESS STACK"); + } + break; + case PHASE1_77_ROAMING_ADDRESS_UPDATE: + if(mac instanceof RoamingAddressUpdate rau) + { + broadcast(message, mac, DecodeEventType.COMMAND, "ROAMING ADDRESS UPDATE"); + } + break; + } + } - if(mac instanceof UnitToUnitVoiceChannelUserExtended uuvcue) - { - if(uuvcue.getServiceOptions().isEncrypted()) + /** + * Queued Response + */ + private void processQueued(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) + { + case PHASE1_61_QUEUED_RESPONSE: + if(mac instanceof QueuedResponse qr) { - updateCurrentCall(DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED, null, message.getTimestamp()); + broadcast(message, mac, DecodeEventType.RESPONSE, "QUEUED - " + qr.getQueuedResponseServiceType() + " REASON:" + qr.getQueuedResponseReason() + " ADDL:" + qr.getAdditionalInfo()); } - else + break; + case MOTOROLA_A6_QUEUED_RESPONSE: + if(mac instanceof MotorolaQueuedResponse qr) { - updateCurrentCall(DecodeEventType.CALL_UNIT_TO_UNIT, null, message.getTimestamp()); + broadcast(message, mac, DecodeEventType.RESPONSE, "QUEUED - " + qr.getQueuedResponseServiceType() + " REASON:" + qr.getQueuedResponseReason() + " ADDL:" + qr.getAdditionalInfo()); } - } + break; } } - private void processUTUVCU(MacMessage message, MacStructure mac) { - if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME) - { - closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME); + /** + * Null Information and Null Avoid Zero Bias + */ + private void processNullInformation(MacMessage message, MacStructure mac) + { + /** + * Notionally, we should close out any current call event here, but that causes timing problems because if the + * control channel creates a call event that is going to be happening on this channel shortly, and we are still + * seeing null info mac messages here, this traffic channel will close out the event that the control channel + * created and then recreate a new event once the actual call starts. So, don't close out the current call + * based solely on the null info in the traffic channel. Ultimately, the existing call event will either be + * updated by a subsequent call, or removed via the traffic channel teardown. + */ + continueState(State.ACTIVE); + } - for(Identifier identifier : mac.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } - } - else + /** + * Unit monitor - places the radio into a traffic channel and forces it to broadcast for safety situations. + */ + private void processRadioUnitMonitor(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) { - for(Identifier identifier : mac.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } - - if(mac instanceof UnitToUnitVoiceChannelUserAbbreviated) - { - UnitToUnitVoiceChannelUserAbbreviated uuvcua = (UnitToUnitVoiceChannelUserAbbreviated)mac; - - if(uuvcua.getServiceOptions().isEncrypted()) + case PHASE1_4C_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED: + if(mac instanceof RadioUnitMonitorCommandAbbreviated rum) { - updateCurrentCall(DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED, null, message.getTimestamp()); + broadcast(message, mac, DecodeEventType.COMMAND, "RADIO UNIT MONITOR" + (rum.isSilentMonitor() ? " (SILENT)" : "") + " TIME:" + rum.getTransmitTime() + " MULTIPLIER:" + rum.getTransmitMultiplier()); } - else + break; + case PHASE1_5D_RADIO_UNIT_MONITOR_COMMAND_OBSOLETE: + //Ignore + break; + case PHASE1_5E_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_ABBREVIATED: + if(mac instanceof RadioUnitMonitorEnhancedCommandAbbreviated rum) { - updateCurrentCall(DecodeEventType.CALL_UNIT_TO_UNIT, null, message.getTimestamp()); + broadcast(message, mac, DecodeEventType.COMMAND, "RADIO UNIT MONITOR" + (rum.isSilentMode() ? " (SILENT)" : "") + " TIME:" + rum.getTransmitTime()); } - } + break; + case PHASE1_CC_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_VCH: + if(mac instanceof RadioUnitMonitorCommandExtendedVCH rum) + { + broadcast(message, mac, DecodeEventType.COMMAND, "RADIO UNIT MONITOR" + (rum.isSilentMonitor() ? " (STEALTH)" : "") + " TIME:" + rum.getTransmitTime() + "MULTIPLIER:" + rum.getTransmitMultiplier()); + } + break; + case PHASE1_CD_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_LCCH: + if(mac instanceof RadioUnitMonitorCommandExtendedLCCH rum) + { + broadcast(message, mac, DecodeEventType.COMMAND, "RADIO UNIT MONITOR" + (rum.isSilentMonitor() ? " (STEALTH)" : "") + " TIME:" + rum.getTransmitTime() + "MULTIPLIER:" + rum.getTransmitMultiplier()); + } + break; + case PHASE1_DE_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_EXTENDED: + if(mac instanceof RadioUnitMonitorEnhancedCommandExtended rum) + { + broadcast(message, mac, DecodeEventType.COMMAND, "RADIO UNIT MONITOR" + (rum.isSilentMode() ? " (STEALTH)" : "") + " TIME:" + rum.getTransmitTime()); + } + break; } } - private void processGVCUExtended(MacMessage message, MacStructure mac) { - if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME) + /** + * Processes GPS messages + */ + private void processGPS(MacMessage message, MacStructure structure) + { + if(structure instanceof L3HarrisTalkerGpsLocation gps) { - closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME); + MutableIdentifierCollection collection = getUpdatedMutableIdentifierCollection(gps); - for(Identifier identifier : mac.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } - } - else - { - for(Identifier identifier : mac.getIdentifiers()) + //Since the L3Harris GPS doesn't have the source radio re-add it here + Identifier fromRadio = getIdentifierCollection().getFromIdentifier(); + if(fromRadio != null) { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); + collection.update(fromRadio); } - if(mac instanceof GroupVoiceChannelUserExtended) - { - GroupVoiceChannelUserExtended gvcue = (GroupVoiceChannelUserExtended)mac; + broadcast(PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, message.getTimestamp()) + .protocol(Protocol.APCO25) + .location(gps.getGeoPosition()).channel(getCurrentChannel()).details("LOCATION: " + + gps.getLocation().toString()) + .identifiers(collection) + .build()); - if(gvcue.getServiceOptions().isEncrypted()) - { - updateCurrentCall(DecodeEventType.CALL_GROUP_ENCRYPTED, null, message.getTimestamp()); - } - else + mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), gps.getLocation(), + message.getTimestamp()); + } + } + + /** + * Location registration + */ + private void processLocationRegistration(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) + { + case PHASE1_6B_LOCATION_REGISTRATION_RESPONSE: + if(mac instanceof LocationRegistrationResponse lrr) { - updateCurrentCall(DecodeEventType.CALL_GROUP, null, message.getTimestamp()); + broadcast(message, mac, DecodeEventType.RESPONSE, "LOCATION REGISTRATION " + lrr.getResponse()); } - } + break; } } - private void processGVCUAbbreviated(MacMessage message, MacStructure mac) { - if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME) + /** + * MAC release. If the channel is preempted by the controller, it can indicate that the channel is converting from + * a traffic channel to a control channel. + */ + private void processMacRelease(MacMessage message, MacStructure mac) + { + if(mac instanceof MacRelease mr) { - closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME); - - for(Identifier identifier : mac.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } + closeCurrentCallEvent(message.getTimestamp(), true, false); + broadcast(message, mac, DecodeEventType.COMMAND, + (mr.isForcedPreemption() ? "FORCED " : "") + "CALL PREEMPTION" + + (mr.isTalkerPreemption() ? " BY USER" : " BY CONTROLLER")); } - else - { - for(Identifier identifier : mac.getIdentifiers()) - { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } - - if(mac instanceof GroupVoiceChannelUserAbbreviated) - { - GroupVoiceChannelUserAbbreviated gvcua = (GroupVoiceChannelUserAbbreviated)mac; + } - if(gvcua.getServiceOptions().isEncrypted()) + /** + * Message Update is the echo (by controller) of the Short Data Message from the SU. Both requests and responses + * are handled by this method. + */ + private void processMessageUpdate(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) + { + case PHASE1_5C_MESSAGE_UPDATE_ABBREVIATED: + if(mac instanceof MessageUpdateAbbreviated mua) { - updateCurrentCall(DecodeEventType.CALL_GROUP_ENCRYPTED, null, message.getTimestamp()); + broadcast(message, mac, getCurrentChannel(), DecodeEventType.SDM, "MESSAGE UPDATE - " + mua.getShortDataMessage()); } - else + break; + case PHASE1_CE_MESSAGE_UPDATE_EXTENDED_LCCH: + if(mac instanceof MessageUpdateExtendedLCCH mue) { - updateCurrentCall(DecodeEventType.CALL_GROUP, null, message.getTimestamp()); + broadcast(message, mac, getCurrentChannel(), DecodeEventType.SDM, "MESSAGE UPDATE - " + mue.getShortDataMessage()); } - } + break; + case PHASE1_DC_MESSAGE_UPDATE_EXTENDED_VCH: + if(mac instanceof MessageUpdateExtendedVCH mue) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.SDM, "MESSAGE UPDATE - " + mue.getShortDataMessage()); + } + break; + } } - private void processEndPushToTalk(MacMessage message, MacStructure mac) { - if(mac instanceof EndPushToTalk) + /** + * Power Control - controller commands the radio to adjust power and provides received signal quality indication + * from the radio + */ + private void processPowerControl(MacMessage message, MacStructure mac) + { + if(mac instanceof PowerControlSignalQuality pcsq) { - //Add the set of identifiers before we close out the call event to ensure they're captured in - //the closing event. - if(mCurrentCallEvent != null) - { - for(Identifier identifier : mac.getIdentifiers()) - { - if(identifier.getRole() == Role.FROM) - { - if(identifier instanceof APCO25RadioIdentifier) - { - int value = ((APCO25RadioIdentifier)identifier).getValue(); + broadcast(message, mac, DecodeEventType.COMMAND, + "ADJUST TRANSMIT POWER - RF:" + pcsq.getRFLevel() + " BER:" + pcsq.getBitErrorRate()); + } + } - //Group call End PTT uses a FROM value of 0xFFFFFF - don't overwrite the correct id - if(value != SYSTEM_CONTROLLER) - { - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } - } - } - else if(identifier.getRole() == Role.TO) - { - if(identifier instanceof APCO25Talkgroup) - { - int value = ((APCO25Talkgroup)identifier).getValue(); + /** + * Paging. + *

+ * Individual paging means the controller wants the individual radio(s) to return to the control channel + * from the current call. + *

+ * Group paging indicates that one or more radios in the current call are part of another talkgroup and that + * talkgroup is also active on the site, so the radio can optionally return to the control channel and pickup the + * group channel and proceed to join the talkgroup call. + */ + private void processPaging(MacMessage message, MacStructure mac) + { + if(mac instanceof IndividualPagingWithPriority ip) + { + boolean p1 = ip.isTalkgroupPriority1(); + broadcast(P25DecodeEvent.builder(DecodeEventType.PAGE, message.getTimestamp()) + .channel(getCurrentChannel()) + .details(p1 ? "PRIORITY " : "" + "USER PAGE-RETURN TO CONTROL CHANNEL") + .identifiers(getIdentifierCollectionForUser(ip.getTargetAddress1(), message.getTimestamp())) + .timeslot(getTimeslot()) + .build()); - //Individual call End PTT uses a TO value of 0 - don't overwrite the correct id - if(value != 0) - { - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); - } - } - } - else + if(ip.getCount() > 1) + { + boolean p2 = ip.isTalkgroupPriority2(); + broadcast(P25DecodeEvent.builder(DecodeEventType.PAGE, message.getTimestamp()) + .channel(getCurrentChannel()) + .details(p2 ? "PRIORITY " : "" + "USER PAGE-RETURN TO CONTROL CHANNEL") + .identifiers(getIdentifierCollectionForUser(ip.getTargetAddress2(), message.getTimestamp())) + .timeslot(getTimeslot()) + .build()); + + if(ip.getCount() > 2) + { + boolean p3 = ip.isTalkgroupPriority3(); + broadcast(P25DecodeEvent.builder(DecodeEventType.PAGE, message.getTimestamp()) + .channel(getCurrentChannel()) + .details(p3 ? "PRIORITY " : "" + "USER PAGE-RETURN TO CONTROL CHANNEL") + .identifiers(getIdentifierCollectionForUser(ip.getTargetAddress3(), message.getTimestamp())) + .timeslot(getTimeslot()) + .build()); + + if(ip.getCount() > 3) { - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); + boolean p4 = ip.isTalkgroupPriority4(); + broadcast(P25DecodeEvent.builder(DecodeEventType.PAGE, message.getTimestamp()) + .channel(getCurrentChannel()) + .details(p4 ? "PRIORITY " : "" + "USER PAGE-RETURN TO CONTROL CHANNEL") + .identifiers(getIdentifierCollectionForUser(ip.getTargetAddress4(), message.getTimestamp())) + .timeslot(getTimeslot()) + .build()); } } } + } + else if(mac instanceof IndirectGroupPagingWithoutPriority igp) + { + broadcast(P25DecodeEvent.builder(DecodeEventType.PAGE, message.getTimestamp()) + .channel(getCurrentChannel()) + .details("GROUP PAGE - TALKGROUP IS ACTIVE ON SITE") + .identifiers(getIdentifierCollectionForUser(igp.getTargetGroup1(), message.getTimestamp())) + .timeslot(getTimeslot()) + .build()); - if(message.getDataUnitID().isFACCH()) + if(igp.getCount() > 1) { - mEndPttOnFacchCounter++; - - //FNE sending 2 or more End PTT in FACCH timeslots indicates a channel teardown event. - if(mEndPttOnFacchCounter > 1) + broadcast(P25DecodeEvent.builder(DecodeEventType.PAGE, message.getTimestamp()) + .channel(getCurrentChannel()) + .details("GROUP PAGE - TALKGROUP IS ACTIVE ON SITE") + .identifiers(getIdentifierCollectionForUser(igp.getTargetGroup2(), message.getTimestamp())) + .timeslot(getTimeslot()) + .build()); + + if(igp.getCount() > 2) { - if(mChannelType == ChannelType.TRAFFIC) - { - closeCurrentCallEvent(message.getTimestamp(), true, MacPduType.MAC_2_END_PTT); - //Don't send a continue state here ... the close() method will send the TEARDOWN state - } - //Otherwise the channel state is set to ACTIVE in anticipation of further call activity - else + broadcast(P25DecodeEvent.builder(DecodeEventType.PAGE, message.getTimestamp()) + .channel(getCurrentChannel()) + .details("GROUP PAGE - TALKGROUP IS ACTIVE ON SITE") + .identifiers(getIdentifierCollectionForUser(igp.getTargetGroup3(), message.getTimestamp())) + .timeslot(getTimeslot()) + .build()); + + if(igp.getCount() > 3) { - closeCurrentCallEvent(message.getTimestamp(), true, MacPduType.MAC_4_ACTIVE); - continueState(State.RESET); + broadcast(P25DecodeEvent.builder(DecodeEventType.PAGE, message.getTimestamp()) + .channel(getCurrentChannel()) + .details("GROUP PAGE - TALKGROUP IS ACTIVE ON SITE") + .identifiers(getIdentifierCollectionForUser(igp.getTargetGroup4(), message.getTimestamp())) + .timeslot(getTimeslot()) + .build()); } } } - else - { - closeCurrentCallEvent(message.getTimestamp(), true, MacPduType.MAC_4_ACTIVE); - } } } - private void processPushToTalk(MacMessage message, MacStructure mac) { - for(Identifier identifier : mac.getIdentifiers()) + /** + * Status query and update provides radio unit and user status reports. + */ + private void processStatus(MacMessage message, MacStructure mac) + { + switch(mac.getOpcode()) { - //Add to the identifier collection after filtering through the patch group manager - getIdentifierCollection().update(mPatchGroupManager.update(identifier)); + case PHASE1_58_STATUS_UPDATE_ABBREVIATED: + if(mac instanceof StatusUpdateAbbreviated sua) + { + broadcast(message, mac, DecodeEventType.STATUS, "STATUS UPDATE - UNIT:" + sua.getUnitStatus() + " USER:" + sua.getUserStatus()); + } + break; + case PHASE1_5A_STATUS_QUERY_ABBREVIATED: + if(mac instanceof StatusQueryAbbreviated) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.QUERY, "STATUS"); + } + break; + case PHASE1_D8_STATUS_UPDATE_EXTENDED_VCH: + if(mac instanceof StatusUpdateExtendedVCH sue) + { + broadcast(message, mac, DecodeEventType.STATUS, "STATUS UPDATE - UNIT:" + sue.getUnitStatus() + " USER:" + sue.getUserStatus()); + } + break; + case PHASE1_D9_STATUS_UPDATE_EXTENDED_LCCH: + if(mac instanceof StatusUpdateExtendedLCCH sue) + { + broadcast(message, mac, DecodeEventType.STATUS, "STATUS UPDATE - UNIT:" + sue.getUnitStatus() + " USER:" + sue.getUserStatus()); + } + break; + case PHASE1_DA_STATUS_QUERY_EXTENDED_VCH: + if(mac instanceof StatusQueryExtendedVCH) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.QUERY, "STATUS"); + } + break; + case PHASE1_DB_STATUS_QUERY_EXTENDED_LCCH: + if(mac instanceof StatusQueryExtendedLCCH) + { + broadcast(message, mac, getCurrentChannel(), DecodeEventType.QUERY, "STATUS"); + } + break; } - PushToTalk ptt = (PushToTalk)mac; - if(ptt.isEncrypted()) - { - updateCurrentCall(DecodeEventType.CALL_ENCRYPTED, ptt.getEncryptionKey().toString(), message.getTimestamp()); - } - else + } + + /** + * Talker Alias + */ + private void processTalkerAlias(MacMessage message, MacStructure mac) + { + if(mac instanceof L3HarrisTalkerAlias talkerAlias) { - updateCurrentCall(DecodeEventType.CALL, null, message.getTimestamp()); + Identifier alias = talkerAlias.getAlias(); + getIdentifierCollection().update(alias); + mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), alias, message.getTimestamp()); } } /** - * Indicates if the identifier argument matches the current (TO) talkgroup for this channel and timeslot - * @param identifier to match - * @return true if the identifier matches the current channel's TO talkgroup + * Unit registration */ - private boolean isCurrentGroup(Identifier identifier) + private void processUnitRegistration(MacMessage message, MacStructure mac) { - if(identifier != null) + switch(mac.getOpcode()) { - for(Identifier id: getIdentifierCollection().getIdentifiers(Role.TO)) - { - if(identifier.equals(id)) - { - return true; - } - } + case PHASE1_6C_UNIT_REGISTRATION_RESPONSE_ABBREVIATED: + case PHASE1_EC_UNIT_REGISTRATION_RESPONSE_EXTENDED: + broadcast(message, mac, getCurrentChannel(), DecodeEventType.RESPONSE, "UNIT REGISTRATION"); + break; + case PHASE1_6D_UNIT_REGISTRATION_COMMAND_ABBREVIATED: + broadcast(message, mac, getCurrentChannel(), DecodeEventType.COMMAND, "UNIT REGISTER"); + break; + case PHASE1_6F_DEREGISTRATION_ACKNOWLEDGE: + broadcast(message, mac, getCurrentChannel(), DecodeEventType.ACKNOWLEDGE, "UNIT DEREGISTERED"); + break; } + } - return false; + + /** + * Creates and broadcasts a decode event. + * + * @param message for the event + * @param mac for the event + * @param channel for the event + * @param eventType of event + * @param details to populate for the event + */ + private void broadcast(MacMessage message, MacStructure mac, IChannelDescriptor channel, DecodeEventType eventType, String details) + { + MutableIdentifierCollection collection = getUpdatedMutableIdentifierCollection(mac); + + broadcast(P25DecodeEvent.builder(eventType, message.getTimestamp()).channel(channel) + .details(details) + .identifiers(collection) + .timeslot(getTimeslot()) + .build()); + } + + /** + * Creates a decode event and broadcasts it for the current channel. + * + * @param message with a timestamp + * @param structure containing identifiers for the event + * @param eventType for the generated event. + * @param details to assign to the generated event. + */ + private void broadcast(MacMessage message, MacStructure structure, DecodeEventType eventType, String details) + { + MutableIdentifierCollection icQueuedResponse = getUpdatedMutableIdentifierCollection(structure); + + broadcast(P25DecodeEvent.builder(eventType, message.getTimestamp()) + .channel(getCurrentChannel()) + .details(details) + .identifiers(icQueuedResponse) + .timeslot(getTimeslot()) + .build()); + } + + /** + * Creates a copy of the current identifier collection with the USER identifiers removed and updated from the + * mac argument identifiers. + * + * @param mac containing updated identifiers to add to the returned collection. + * @return mutable identifier collection. + */ + private MutableIdentifierCollection getUpdatedMutableIdentifierCollection(MacStructure mac) + { + MutableIdentifierCollection icQueuedResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icQueuedResponse.remove(IdentifierClass.USER); + icQueuedResponse.update(mac.getIdentifiers()); + return icQueuedResponse; } /** * Broadcasts a state continuation. If we're currently in a call, then we broadcast a call continuation, otherwise * we broadcast a continuation of the specified state. + * * @param state to continue */ private void continueState(State state) { - if(mCurrentCallEvent != null) - { - broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot())); - } - else - { - broadcast(new DecoderStateEvent(this, Event.DECODE, state, getTimeslot())); - } + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, state, getTimeslot())); } /** * Updates or creates a current call event. * - * @param type of call that will be used as an event description + * @param macOpcode for the update + * @param serviceOptions for the call * @param details of the call (optional) * @param timestamp of the message indicating a call or continuation */ - private void updateCurrentCall(DecodeEventType type, String details, long timestamp) + private void updateCurrentCall(MacOpcode macOpcode, ServiceOptions serviceOptions, String details, long timestamp) { - if(mCurrentCallEvent == null) - { - if(type == null) - { - type = DecodeEventType.CALL; - } - - mCurrentCallEvent = P25DecodeEvent.builder(type, timestamp) - .channel(getCurrentChannel()) - .details(details) - .identifiers(getIdentifierCollection().copyOf()) - .build(); - - broadcast(mCurrentCallEvent); + mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), getCurrentChannel(), serviceOptions, macOpcode, getIdentifierCollection().copyOf(), timestamp, details); - if(isEncrypted()) - { - broadcast(new DecoderStateEvent(this, Event.START, State.ENCRYPTED, getTimeslot())); - } - else - { - broadcast(new DecoderStateEvent(this, Event.START, State.CALL, getTimeslot())); - } + if(isEncrypted()) + { + broadcast(new DecoderStateEvent(this, Event.START, State.ENCRYPTED, getTimeslot())); } else { - if(details != null) - { - mCurrentCallEvent.setDetails(details); - } - - mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf()); - mCurrentCallEvent.end(timestamp); - broadcast(mCurrentCallEvent); + broadcast(new DecoderStateEvent(this, Event.START, State.CALL, getTimeslot())); } + } /** * Ends/closes the current call event. * * @param timestamp of the message that indicates the event has ended. - * @param resetIdentifiers to reset the FROM/TO identifiers - * @param pduType of the message that caused the close call event - to determine channel state after call + * @param resetIdentifiers to reset the FROM/TO identifiers (true) or reset just the FROM identifiers (false) + * @param isIdleNull to indicate if the calling trigger is an IDLE/NULL message */ - private void closeCurrentCallEvent(long timestamp, boolean resetIdentifiers, MacPduType pduType) + private void closeCurrentCallEvent(long timestamp, boolean resetIdentifiers, boolean isIdleNull) { - if(mCurrentCallEvent != null) - { - //Refresh the identifier collection before we close out the event - mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf()); - mCurrentCallEvent.end(timestamp); - broadcast(mCurrentCallEvent); - mCurrentCallEvent = null; - - broadcast(new DecoderStateEvent(this, Event.END, getStateFromPduType(pduType), getTimeslot())); + mTrafficChannelManager.closeP2CallEvent(getCurrentFrequency(), getTimeslot(), timestamp, isIdleNull); - if(resetIdentifiers) - { - getIdentifierCollection().remove(IdentifierClass.USER); - } - else - { - //Only clear the from identifier at this point ... the channel may still be allocated to the TO talkgroup - getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); - } + if(resetIdentifiers) + { + getIdentifierCollection().remove(IdentifierClass.USER); } - else if(pduType == MacPduType.MAC_2_END_PTT) + else { - broadcast(new DecoderStateEvent(this, Event.END, getStateFromPduType(pduType), getTimeslot())); + //Only clear the from identifier(s) at this point ... the channel may still be allocated to the TO talkgroup + getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); } } @@ -1138,7 +1918,11 @@ private boolean isEncrypted() @Override public String getActivitySummary() { - return mNetworkConfigurationMonitor.getActivitySummary(); + StringBuilder sb = new StringBuilder(); + sb.append(mNetworkConfigurationMonitor.getActivitySummary()); + sb.append("\n"); + sb.append(mPatchGroupManager.getPatchGroupSummary()); + return sb.toString(); } @Override @@ -1152,6 +1936,14 @@ public void receiveDecoderStateEvent(DecoderStateEvent event) resetState(); mNetworkConfigurationMonitor.reset(); break; + case NOTIFICATION_SOURCE_FREQUENCY: + long frequency = event.getFrequency(); + + //Notify the TCM that our control frequency has changed. + if(mChannel.isStandardChannel()) + { + mTrafficChannelManager.setCurrentControlFrequency(frequency, mChannel); + } default: break; } @@ -1165,7 +1957,7 @@ public void start() mPatchGroupManager.clear(); //Change the default (45-second) traffic channel timeout to 1 second - if(mChannelType == ChannelType.TRAFFIC) + if(mChannel.isTrafficChannel()) { broadcast(new ChangeChannelTimeoutEvent(this, ChannelType.TRAFFIC, 1000, getTimeslot())); } @@ -1182,4 +1974,4 @@ public void stop() super.stop(); mPatchGroupManager.clear(); } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java index fc771dab9..2c6f0028a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageFramer.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -25,22 +25,22 @@ import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.dsp.symbol.Dibit; import io.github.dsheirer.dsp.symbol.ISyncDetectListener; +import io.github.dsheirer.identifier.patch.PatchGroupManager; import io.github.dsheirer.log.ApplicationLog; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageProviderModule; import io.github.dsheirer.module.ProcessingChain; import io.github.dsheirer.module.decode.DecoderType; +import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager; import io.github.dsheirer.module.decode.p25.audio.P25P2AudioModule; import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence; import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; +import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.record.AudioRecordingManager; import io.github.dsheirer.record.binary.BinaryReader; import io.github.dsheirer.sample.Listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; @@ -49,6 +49,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * P25 Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors @@ -76,11 +78,6 @@ public P25P2MessageFramer(IPhaseLockedLoop phaseLockedLoop, int bitRate) mBitRate = bitRate; } - public P25P2MessageFramer(int bitRate) - { - this(null, bitRate); - } - /** * Sets or updates the scramble parameters for the current channel * @param scrambleParameters @@ -233,10 +230,14 @@ public static void main(String[] args) ProcessingChain processingChain = new ProcessingChain(channel, new AliasModel()); processingChain.addAudioSegmentListener(recordingManager); - processingChain.addModule(new P25P2DecoderState(channel, 0)); - processingChain.addModule(new P25P2DecoderState(channel, 1)); - processingChain.addModule(new P25P2AudioModule(userPreferences, 0, aliasList)); - processingChain.addModule(new P25P2AudioModule(userPreferences, 1, aliasList)); + P25TrafficChannelManager trafficChannelManager = new P25TrafficChannelManager(channel); + PatchGroupManager patchGroupManager = new PatchGroupManager(); + processingChain.addModule(new P25P2DecoderState(channel, P25P2Message.TIMESLOT_1, trafficChannelManager, + patchGroupManager)); + processingChain.addModule(new P25P2DecoderState(channel, P25P2Message.TIMESLOT_2, trafficChannelManager, + patchGroupManager)); + processingChain.addModule(new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_1, aliasList)); + processingChain.addModule(new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_2, aliasList)); MessageProviderModule messageProviderModule = new MessageProviderModule(); processingChain.addModule(messageProviderModule); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageProcessor.java index bcf16dbe2..78b165761 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2MessageProcessor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -21,12 +21,16 @@ import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.SyncLossMessage; +import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence; import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequenceProcessor; +import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; import io.github.dsheirer.module.decode.p25.phase2.message.SuperFrameFragment; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureMultiFragment; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MultiFragmentContinuationMessage; import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractSignalingTimeslot; import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot; import io.github.dsheirer.module.decode.p25.phase2.timeslot.Timeslot; @@ -43,18 +47,39 @@ public class P25P2MessageProcessor implements Listener { private final static Logger mLog = LoggerFactory.getLogger(P25P2MessageProcessor.class); - private EncryptionSynchronizationSequenceProcessor mESSProcessor0 = new EncryptionSynchronizationSequenceProcessor(0); - private EncryptionSynchronizationSequenceProcessor mESSProcessor1 = new EncryptionSynchronizationSequenceProcessor(1); + private EncryptionSynchronizationSequenceProcessor mESSProcessor1 = new EncryptionSynchronizationSequenceProcessor(P25P2Message.TIMESLOT_1); + private EncryptionSynchronizationSequenceProcessor mESSProcessor2 = new EncryptionSynchronizationSequenceProcessor(P25P2Message.TIMESLOT_2); + private MacMessage mMacMessageWithMultiFragment1; + private MacStructureMultiFragment mMacStructureMultiFragment1; + private MacStructureMultiFragment mMacStructureMultiFragment2; private Listener mMessageListener; //Map of up to 16 band identifiers per RFSS. These identifier update messages are inserted into any message that // conveys channel information so that the uplink/downlink frequencies can be calculated private Map mFrequencyBandMap = new TreeMap(); + /** + * Constructs an instance + */ public P25P2MessageProcessor() { } + /** + * Preloads frequency band (ie identifier update) content from the control channel when this is a traffic channel. + * @param content to preload + */ + public void preload(P25FrequencyBandPreloadDataContent content) + { + if(content.hasData()) + { + for(IFrequencyBand frequencyBand: content.getData()) + { + mFrequencyBandMap.put(frequencyBand.getIdentifier(), frequencyBand); + } + } + } + @Override public void receive(IMessage message) { @@ -64,6 +89,9 @@ public void receive(IMessage message) { SuperFrameFragment sff = (SuperFrameFragment)message; + mMessageListener.receive(sff.getIISCH1()); + mMessageListener.receive(sff.getIISCH2()); + for(Timeslot timeslot: sff.getTimeslots()) { if(timeslot instanceof AbstractSignalingTimeslot) @@ -72,11 +100,107 @@ public void receive(IMessage message) for(MacMessage macMessage: ast.getMacMessages()) { - /* Insert frequency band identifier update messages into channel-type messages */ - if(macMessage instanceof IFrequencyBandReceiver) + switch(macMessage.getMacPduType()) + { + case MAC_1_PTT: + case MAC_2_END_PTT: + case MAC_6_HANGTIME: + case MAC_3_IDLE: + if (timeslot.getTimeslot() == P25P2Message.TIMESLOT_1) + { + mESSProcessor1.reset(); + } + else + { + mESSProcessor2.reset(); + } + break; + } + + //Process any multi-fragment mac structures transmitted on the LCCH for reassembly + if(macMessage.getMacStructure() instanceof MacStructureMultiFragment mf) + { + if(macMessage.getTimeslot() == P25P2Message.TIMESLOT_1) + { + mMacMessageWithMultiFragment1 = macMessage; + mMacStructureMultiFragment1 = mf; + } + else + { + mMacStructureMultiFragment2 = mf; + } + } + //Process multi-fragment continuation messages transmitted on the LCCH + else if(macMessage.getMacStructure() instanceof MultiFragmentContinuationMessage mfcm) + { + if(macMessage.getTimeslot() == P25P2Message.TIMESLOT_1) + { + if(mMacStructureMultiFragment1 != null) + { + mMacStructureMultiFragment1.addContinuationMessage(mfcm); + //Replace the continuation message with the assembled base message. + macMessage.setMacStructure(mMacStructureMultiFragment1); + + //Cleanup if we're now complete + if(mMacStructureMultiFragment1.isComplete()) + { + mMacStructureMultiFragment1 = null; + mMacMessageWithMultiFragment1 = null; + } + } + } + else + { + if(mMacStructureMultiFragment2 != null) + { + mMacStructureMultiFragment2.addContinuationMessage(mfcm); + //Replace the continuation message with the assembled base message. + macMessage.setMacStructure(mMacStructureMultiFragment2); + + //Cleanup if we're now complete + if(mMacStructureMultiFragment2.isComplete()) + { + mMacStructureMultiFragment2 = null; + } + } + /** + * Single slot LCCH can transmit the continuation fragments on either timeslot + * so we attempt to combine a continuation fragment from timeslot 2 onto the base + * message from timeslot 1 and then resend the original timeslot 1 as the carrier + * message and also push the timeslot 2 mac message and mac structure to listener. + */ + else if(mMacMessageWithMultiFragment1 != null && mMacMessageWithMultiFragment1 != null) + { + mMacStructureMultiFragment1.addContinuationMessage(mfcm); + //Re-broadcast the original timeslot 1 mac message with the updated structure + mMessageListener.receive(mMacMessageWithMultiFragment1); + + //Cleanup if we're now complete + if(mMacStructureMultiFragment1.isComplete()) + { + mMacStructureMultiFragment1 = null; + mMacMessageWithMultiFragment1 = null; + } + } + } + } + else { - IFrequencyBandReceiver receiver = (IFrequencyBandReceiver)macMessage; + //If it's not a multi-fragment or continuation message, then we're not in assembly mode. + if(macMessage.getTimeslot() == P25P2Message.TIMESLOT_1) + { + mMacStructureMultiFragment1 = null; + mMacMessageWithMultiFragment1 = null; + } + else + { + mMacStructureMultiFragment2 = null; + } + } + /* Insert frequency band identifier update messages into channel-type messages */ + if(macMessage.getMacStructure() instanceof IFrequencyBandReceiver receiver) + { List channels = receiver.getChannels(); for(IChannelDescriptor channel : channels) @@ -95,29 +219,11 @@ public void receive(IMessage message) /* Store band identifiers so that they can be injected into channel * type messages */ - if(macMessage instanceof IFrequencyBand) + if(macMessage.getMacStructure() instanceof IFrequencyBand bandIdentifier) { - IFrequencyBand bandIdentifier = (IFrequencyBand)macMessage; mFrequencyBandMap.put(bandIdentifier.getIdentifier(), bandIdentifier); } - switch(macMessage.getMacPduType()) - { - case MAC_1_PTT: - case MAC_2_END_PTT: - case MAC_6_HANGTIME: - case MAC_3_IDLE: - if (timeslot.getTimeslot() == 0) - { - mESSProcessor0.reset(); - } - else - { - mESSProcessor1.reset(); - } - break; - } - mMessageListener.receive(macMessage); } } @@ -125,36 +231,36 @@ else if(timeslot instanceof AbstractVoiceTimeslot) { mMessageListener.receive(timeslot); - if(timeslot.getTimeslot() == 0) + if(timeslot.getTimeslot() == P25P2Message.TIMESLOT_1) { - mESSProcessor0.process((AbstractVoiceTimeslot)timeslot); + mESSProcessor1.process((AbstractVoiceTimeslot)timeslot); if(timeslot instanceof Voice2Timeslot) { - EncryptionSynchronizationSequence ess = mESSProcessor0.getSequence(); + EncryptionSynchronizationSequence ess = mESSProcessor1.getSequence(); if(ess != null) { mMessageListener.receive(ess); } - mESSProcessor0.reset(); + mESSProcessor1.reset(); } } else { - mESSProcessor1.process((AbstractVoiceTimeslot)timeslot); + mESSProcessor2.process((AbstractVoiceTimeslot)timeslot); if(timeslot instanceof Voice2Timeslot) { - EncryptionSynchronizationSequence ess = mESSProcessor1.getSequence(); + EncryptionSynchronizationSequence ess = mESSProcessor2.getSequence(); if(ess != null) { mMessageListener.receive(ess); } - mESSProcessor1.reset(); + mESSProcessor2.reset(); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2NetworkConfigurationMonitor.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2NetworkConfigurationMonitor.java index 37ac78ad6..399b9e194 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2NetworkConfigurationMonitor.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2NetworkConfigurationMonitor.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2; @@ -26,27 +23,32 @@ import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastExtendedExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdate; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdateTDMA; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdateTDMAAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdateTDMAExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdateVUHF; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RfssStatusBroadcastAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RfssStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SecondaryControlChannelBroadcastAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RfssStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RfssStatusBroadcastImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SNDCPDataChannelAnnouncement; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SecondaryControlChannelBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SecondaryControlChannelBroadcastImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SystemServiceBroadcast; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.function.Consumer; -import java.util.function.Function; - /** * Tracks the network configuration details of a P25 Phase 2 network from the broadcast messages */ @@ -57,22 +59,26 @@ public class P25P2NetworkConfigurationMonitor private Map mFrequencyBandMap = new HashMap<>(); //Network Status Messages - private NetworkStatusBroadcastAbbreviated mNetworkStatusBroadcastAbbreviated; - private NetworkStatusBroadcastExtended mNetworkStatusBroadcastExtended; + private NetworkStatusBroadcastImplicit mNetworkStatusBroadcastImplicit; + private NetworkStatusBroadcastExplicit mNetworkStatusBroadcastExplicit; //Current Site Status Messages - private RfssStatusBroadcastAbbreviated mRFSSStatusBroadcastAbbreviated; - private RfssStatusBroadcastExtended mRFSSStatusBroadcastExtended; + private RfssStatusBroadcastImplicit mRFSSStatusBroadcastImplicit; + private RfssStatusBroadcastExplicit mRFSSStatusBroadcastExplicit; //Current Site Secondary Control Channels private Map mSecondaryControlChannels = new TreeMap<>(); + //SNDCP Data Channel + private SNDCPDataChannelAnnouncement mSNDCPDataChannelAnnouncement; + //Current Site Services private SystemServiceBroadcast mSystemServiceBroadcast; //Neighbor Sites - private Map mNeighborSitesAbbreviated = new HashMap<>(); - private Map mNeighborSitesExtended = new HashMap<>(); + private Map mNeighborSitesAbbreviated = new HashMap<>(); + private Map mNeighborSitesExtended = new HashMap<>(); + private Map mNeighborSitesExtendedExplicit = new HashMap<>(); /** * Constructs an instance. @@ -112,92 +118,100 @@ public void processMacMessage(MacMessage message) switch((mac.getOpcode())) { - case PHASE1_115_IDENTIFIER_UPDATE_TDMA: - if(mac instanceof FrequencyBandUpdateTDMA) + case PHASE1_73_IDENTIFIER_UPDATE_TDMA_ABBREVIATED: + if(mac instanceof FrequencyBandUpdateTDMAAbbreviated tdma) { - FrequencyBandUpdateTDMA tdma = (FrequencyBandUpdateTDMA)mac; mFrequencyBandMap.put(tdma.getIdentifier(), tdma); } break; - case PHASE1_116_IDENTIFIER_UPDATE_V_UHF: - if(mac instanceof FrequencyBandUpdateVUHF) + case PHASE1_74_IDENTIFIER_UPDATE_V_UHF: + if(mac instanceof FrequencyBandUpdateVUHF vhf) { - FrequencyBandUpdateVUHF vhf = (FrequencyBandUpdateVUHF)mac; mFrequencyBandMap.put(vhf.getIdentifier(), vhf); } break; - case PHASE1_120_SYSTEM_SERVICE_BROADCAST: - if(mac instanceof SystemServiceBroadcast) + case PHASE1_78_SYSTEM_SERVICE_BROADCAST: + if(mac instanceof SystemServiceBroadcast ssb) { - mSystemServiceBroadcast = (SystemServiceBroadcast)mac; + mSystemServiceBroadcast = ssb; } break; - case PHASE1_121_SECONDARY_CONTROL_CHANNEL_BROADCAST_ABBREVIATED: - if(mac instanceof SecondaryControlChannelBroadcastAbbreviated) + case PHASE1_79_SECONDARY_CONTROL_CHANNEL_BROADCAST_IMPLICIT: + if(mac instanceof SecondaryControlChannelBroadcastImplicit sccba) { - SecondaryControlChannelBroadcastAbbreviated sccba = (SecondaryControlChannelBroadcastAbbreviated)mac; - for(IChannelDescriptor channel: sccba.getChannels()) { mSecondaryControlChannels.put(channel.toString(), channel); } } break; - case PHASE1_122_RFSS_STATUS_BROADCAST_ABBREVIATED: - if(mac instanceof RfssStatusBroadcastAbbreviated) + case PHASE1_7A_RFSS_STATUS_BROADCAST_IMPLICIT: + if(mac instanceof RfssStatusBroadcastImplicit rsbe) { - mRFSSStatusBroadcastAbbreviated = (RfssStatusBroadcastAbbreviated)mac; + mRFSSStatusBroadcastImplicit = rsbe; } break; - case PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED: - if(mac instanceof NetworkStatusBroadcastAbbreviated) + case PHASE1_7B_NETWORK_STATUS_BROADCAST_IMPLICIT: + if(mac instanceof NetworkStatusBroadcastImplicit nsbe) { - mNetworkStatusBroadcastAbbreviated = (NetworkStatusBroadcastAbbreviated)mac; + mNetworkStatusBroadcastImplicit = nsbe; } break; - case PHASE1_124_ADJACENT_STATUS_BROADCAST_ABBREVIATED: - if(mac instanceof AdjacentStatusBroadcastAbbreviated) + case PHASE1_7C_ADJACENT_STATUS_BROADCAST_IMPLICIT: + if(mac instanceof AdjacentStatusBroadcastImplicit asba) { - AdjacentStatusBroadcastAbbreviated asba = (AdjacentStatusBroadcastAbbreviated)mac; mNeighborSitesAbbreviated.put((int)asba.getSite().getValue(), asba); } break; - case PHASE1_125_IDENTIFIER_UPDATE: - if(mac instanceof FrequencyBandUpdate) + case PHASE1_7D_IDENTIFIER_UPDATE: + if(mac instanceof FrequencyBandUpdate band) { - FrequencyBandUpdate band = (FrequencyBandUpdate)mac; mFrequencyBandMap.put(band.getIdentifier(), band); } break; - case PHASE1_233_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - if(mac instanceof SecondaryControlChannelBroadcastExplicit) + case PHASE1_D6_SNDCP_DATA_CHANNEL_ANNOUNCEMENT: + if(mac instanceof SNDCPDataChannelAnnouncement s) + { + mSNDCPDataChannelAnnouncement = s; + } + break; + case PHASE1_E9_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + if(mac instanceof SecondaryControlChannelBroadcastExplicit sccbe) { - SecondaryControlChannelBroadcastExplicit sccbe = (SecondaryControlChannelBroadcastExplicit)mac; - for(IChannelDescriptor channel: sccbe.getChannels()) { mSecondaryControlChannels.put(channel.toString(), channel); } } break; - case PHASE1_250_RFSS_STATUS_BROADCAST_EXTENDED: - if(mac instanceof RfssStatusBroadcastExtended) + case PHASE1_F3_IDENTIFIER_UPDATE_TDMA_EXTENDED: + if(mac instanceof FrequencyBandUpdateTDMAExtended tdma) { - mRFSSStatusBroadcastExtended = (RfssStatusBroadcastExtended)mac; + mFrequencyBandMap.put(tdma.getIdentifier(), tdma); + } + break; + case PHASE1_FA_RFSS_STATUS_BROADCAST_EXPLICIT: + if(mac instanceof RfssStatusBroadcastExplicit rsbe) + { + mRFSSStatusBroadcastExplicit = rsbe; } break; - case PHASE1_251_NETWORK_STATUS_BROADCAST_EXTENDED: - if(mac instanceof NetworkStatusBroadcastExtended) + case PHASE1_FB_NETWORK_STATUS_BROADCAST_EXPLICIT: + if(mac instanceof NetworkStatusBroadcastExplicit nsbe) { - mNetworkStatusBroadcastExtended = (NetworkStatusBroadcastExtended)mac; + mNetworkStatusBroadcastExplicit = nsbe; } break; - case PHASE1_252_ADJACENT_STATUS_BROADCAST_EXTENDED: - if(mac instanceof AdjacentStatusBroadcastExtended) + case PHASE1_FC_ADJACENT_STATUS_BROADCAST_EXPLICIT: + if(mac instanceof AdjacentStatusBroadcastExplicit asbe) { - AdjacentStatusBroadcastExtended asbe = (AdjacentStatusBroadcastExtended)mac; mNeighborSitesExtended.put((int)asbe.getSite().getValue(), asbe); } + case PHASE1_FE_ADJACENT_STATUS_BROADCAST_EXTENDED_EXPLICIT: + if(mac instanceof AdjacentStatusBroadcastExtendedExplicit a) + { + mNeighborSitesExtendedExplicit.put(a.getSite().getValue(), a); + } break; } } @@ -205,10 +219,10 @@ public void processMacMessage(MacMessage message) public void reset() { mFrequencyBandMap.clear(); - mNetworkStatusBroadcastAbbreviated = null; - mNetworkStatusBroadcastExtended = null; - mRFSSStatusBroadcastAbbreviated = null; - mRFSSStatusBroadcastExtended = null; + mNetworkStatusBroadcastImplicit = null; + mNetworkStatusBroadcastExplicit = null; + mRFSSStatusBroadcastImplicit = null; + mRFSSStatusBroadcastExplicit = null; mSecondaryControlChannels.clear(); mSystemServiceBroadcast = null; mNeighborSitesAbbreviated.clear(); @@ -222,19 +236,19 @@ public String getActivitySummary() sb.append("Activity Summary - Decoder:P25 Phase 2"); sb.append("\n\nNetwork\n"); - if(mNetworkStatusBroadcastAbbreviated != null) + if(mNetworkStatusBroadcastImplicit != null) { - sb.append(" WACN:").append(format(mNetworkStatusBroadcastAbbreviated.getWACN(), 5)); - sb.append(" SYSTEM:").append(format(mNetworkStatusBroadcastAbbreviated.getSystem(), 3)); - sb.append(" NAC:").append(format(mNetworkStatusBroadcastAbbreviated.getNAC(), 3)); - sb.append(" LRA:").append(format(mNetworkStatusBroadcastAbbreviated.getLRA(), 2)); + sb.append(" WACN:").append(format(mNetworkStatusBroadcastImplicit.getWACN(), 5)); + sb.append(" SYSTEM:").append(format(mNetworkStatusBroadcastImplicit.getSystem(), 3)); + sb.append(" NAC:").append(format(mNetworkStatusBroadcastImplicit.getNAC(), 3)); + sb.append(" LRA:").append(format(mNetworkStatusBroadcastImplicit.getLRA(), 2)); } - else if(mNetworkStatusBroadcastExtended != null) + else if(mNetworkStatusBroadcastExplicit != null) { - sb.append(" WACN:").append(format(mNetworkStatusBroadcastExtended.getWACN(), 5)); - sb.append(" SYSTEM:").append(format(mNetworkStatusBroadcastExtended.getSystem(), 3)); - sb.append(" NAC:").append(format(mNetworkStatusBroadcastExtended.getNAC(), 3)); - sb.append(" LRA:").append(format(mNetworkStatusBroadcastExtended.getLRA(), 2)); + sb.append(" WACN:").append(format(mNetworkStatusBroadcastExplicit.getWACN(), 5)); + sb.append(" SYSTEM:").append(format(mNetworkStatusBroadcastExplicit.getSystem(), 3)); + sb.append(" NAC:").append(format(mNetworkStatusBroadcastExplicit.getNAC(), 3)); + sb.append(" LRA:").append(format(mNetworkStatusBroadcastExplicit.getLRA(), 2)); } else { @@ -242,29 +256,34 @@ else if(mNetworkStatusBroadcastExtended != null) } sb.append("\n\nCurrent Site\n"); - if(mRFSSStatusBroadcastAbbreviated != null) + if(mRFSSStatusBroadcastImplicit != null) { - sb.append(" SYSTEM:").append(format(mRFSSStatusBroadcastAbbreviated.getSystem(), 3)); - sb.append(" RFSS:").append(format(mRFSSStatusBroadcastAbbreviated.getRFSS(), 2)); - sb.append(" SITE:").append(format(mRFSSStatusBroadcastAbbreviated.getSite(), 2)); - sb.append(" LRA:").append(format(mRFSSStatusBroadcastAbbreviated.getLRA(), 2)); - sb.append(" PRI CONTROL CHANNEL:").append(mRFSSStatusBroadcastAbbreviated.getChannel()); - sb.append(" DOWNLINK:").append(mRFSSStatusBroadcastAbbreviated.getChannel().getDownlinkFrequency()); - sb.append(" UPLINK:").append(mRFSSStatusBroadcastAbbreviated.getChannel().getUplinkFrequency()).append("\n"); + sb.append(" SYSTEM:").append(format(mRFSSStatusBroadcastImplicit.getSystem(), 3)); + sb.append(" RFSS:").append(format(mRFSSStatusBroadcastImplicit.getRFSS(), 2)); + sb.append(" SITE:").append(format(mRFSSStatusBroadcastImplicit.getSite(), 2)); + sb.append(" LRA:").append(format(mRFSSStatusBroadcastImplicit.getLRA(), 2)); + sb.append(" PRI CONTROL CHANNEL:").append(mRFSSStatusBroadcastImplicit.getChannel()); + sb.append(" DOWNLINK:").append(mRFSSStatusBroadcastImplicit.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(mRFSSStatusBroadcastImplicit.getChannel().getUplinkFrequency()).append("\n"); } - else if(mRFSSStatusBroadcastExtended != null) + else if(mRFSSStatusBroadcastExplicit != null) { - sb.append(" SYSTEM:").append(format(mRFSSStatusBroadcastExtended.getSystem(), 3)); - sb.append(" RFSS:").append(format(mRFSSStatusBroadcastExtended.getRFSS(), 2)); - sb.append(" SITE:").append(format(mRFSSStatusBroadcastExtended.getSite(), 2)); - sb.append(" LRA:").append(format(mRFSSStatusBroadcastExtended.getLRA(), 2)); - sb.append(" PRI CONTROL CHANNEL:").append(mRFSSStatusBroadcastExtended.getChannel()); - sb.append(" DOWNLINK:").append(mRFSSStatusBroadcastExtended.getChannel().getDownlinkFrequency()); - sb.append(" UPLINK:").append(mRFSSStatusBroadcastExtended.getChannel().getUplinkFrequency()).append("\n"); + sb.append(" SYSTEM:").append(format(mRFSSStatusBroadcastExplicit.getSystem(), 3)); + sb.append(" RFSS:").append(format(mRFSSStatusBroadcastExplicit.getRFSS(), 2)); + sb.append(" SITE:").append(format(mRFSSStatusBroadcastExplicit.getSite(), 2)); + sb.append(" LRA:").append(format(mRFSSStatusBroadcastExplicit.getLRA(), 2)); + sb.append(" PRI CONTROL CHANNEL:").append(mRFSSStatusBroadcastExplicit.getChannel()); + sb.append(" DOWNLINK:").append(mRFSSStatusBroadcastExplicit.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(mRFSSStatusBroadcastExplicit.getChannel().getUplinkFrequency()).append("\n"); } else { - sb.append(" UNKNOWN"); + sb.append(" UNKNOWN\n"); + } + + if(mSNDCPDataChannelAnnouncement != null) + { + sb.append(" DATA CHANNEL:").append(mSNDCPDataChannelAnnouncement.getChannel()).append("\n"); } if(!mSecondaryControlChannels.isEmpty()) @@ -291,6 +310,7 @@ else if(mRFSSStatusBroadcastExtended != null) Set sites = new TreeSet<>(); sites.addAll(mNeighborSitesAbbreviated.keySet()); sites.addAll(mNeighborSitesExtended.keySet()); + sites.addAll(mNeighborSitesExtendedExplicit.keySet()); if(sites.isEmpty()) { @@ -303,7 +323,7 @@ else if(mRFSSStatusBroadcastExtended != null) .forEach(site -> { if(mNeighborSitesAbbreviated.containsKey(site)) { - AdjacentStatusBroadcastAbbreviated asb = mNeighborSitesAbbreviated.get(site); + AdjacentStatusBroadcastImplicit asb = mNeighborSitesAbbreviated.get(site); sb.append(" SYSTEM:").append(format(asb.getSystem(), 3)); sb.append(" RFSS:").append(format(asb.getRFSS(), 2)); sb.append(" SITE:").append(format(asb.getSite(), 2)); @@ -315,7 +335,19 @@ else if(mRFSSStatusBroadcastExtended != null) } else if(mNeighborSitesExtended.containsKey(site)) { - AdjacentStatusBroadcastExtended asb = mNeighborSitesExtended.get(site); + AdjacentStatusBroadcastExplicit asb = mNeighborSitesExtended.get(site); + sb.append(" SYSTEM:").append(format(asb.getSystem(), 3)); + sb.append(" RFSS:").append(format(asb.getRFSS(), 2)); + sb.append(" SITE:").append(format(asb.getSite(), 2)); + sb.append(" LRA:").append(format(asb.getLRA(), 2)); + sb.append(" CHANNEL:").append(asb.getChannel()); + sb.append(" DOWNLINK:").append(asb.getChannel().getDownlinkFrequency()); + sb.append(" UPLINK:").append(asb.getChannel().getUplinkFrequency()); + sb.append(" STATUS:").append(asb.getSiteFlags()).append("\n"); + } + else if(mNeighborSitesExtendedExplicit.containsKey(site)) + { + AdjacentStatusBroadcastExtendedExplicit asb = mNeighborSitesExtendedExplicit.get(site); sb.append(" SYSTEM:").append(format(asb.getSystem(), 3)); sb.append(" RFSS:").append(format(asb.getRFSS(), 2)); sb.append(" SITE:").append(format(asb.getSite(), 2)); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2SuperFrameDetector.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2SuperFrameDetector.java index 532d14667..3a4c55caf 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2SuperFrameDetector.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2SuperFrameDetector.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2; @@ -31,40 +28,91 @@ import io.github.dsheirer.module.decode.p25.phase2.message.SuperFrameFragment; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastImplicit; import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractSignalingTimeslot; import io.github.dsheirer.module.decode.p25.phase2.timeslot.ScramblingSequence; import io.github.dsheirer.module.decode.p25.phase2.timeslot.Timeslot; import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.sample.Listener; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; - /** - * APCO25 Phase 2 super-frame fragment detector uses a sync pattern detector and a circular - * dibit buffer to detect sync patterns and correctly frame a 1440-bit super-frame fragment - * containing 4 timeslots and surrounding ISCH messaging. + * APCO25 Phase 2 super-frame fragment detector uses a pair of sync pattern detectors and a circular dibit buffer to + * detect sync patterns and correctly frame a (720-dibit / 1440-bit) super-frame fragment containing 4x timeslots and + * 4x ISCH fragments. The first ISCH fragment consisting of 20-bits from the preceding fragment and 20-bits from + * the current fragment are reassembled into a contiguous ISCH chunk. The layout for the super frame fragment is: + * + * I-ISCH1 + TIMESLOT1 + I-ISCH2 + TIMESLOT2 + S-SISCH1(sync1) + TIMESLOT3 + S-SISCH2(sync2) + TIMESLOT4 */ public class P25P2SuperFrameDetector implements Listener, ISyncDetectListener { private final static Logger mLog = LoggerFactory.getLogger(P25P2SuperFrameDetector.class); + /** + * Number of dibits that we use to oversize the fragment delay buffer where the total oversize is 2x this quantity + * for padding the left and padding the right by this quantity. + */ + private static final int FRAGMENT_BUFFER_OVERSIZE = 2; //Dibits + + /** + * A super frame fragment is 720 dibits or 1440 bits long. + */ private static final int FRAGMENT_DIBIT_LENGTH = 720; + + /** + * If we misalign by detecting the first sync pattern in the stream at the second sync detector, we can simply + * shift the stream to the left to align both the sync 1 and sync 2 patterns with the detectors. The sync patterns + * are separated by one timeslot (160 dibits) and one ISCH (20 dibits). + */ private static final int DIBIT_COUNT_MISALIGNED_SYNC = FRAGMENT_DIBIT_LENGTH - 180; + + /** + * Threshold for broadcasting a sync loss message once per second. Size is the quantity of dibits per second (3000) + * plus the length of one super frame (720). + */ private static final int BROADCAST_SYNC_LOSS_DIBIT_COUNT = 3720; - private static final int DIBIT_DELAY_BUFFER_INDEX_SYNC_1 = 360; - private static final int DIBIT_DELAY_BUFFER_INDEX_SYNC_2 = 540; - private static final int SYNCHRONIZED_SYNC_MATCH_THRESHOLD = 10; + + /** + * S-ISCH Sync pattern 1 is 2x timeslots (160 dibits ea.) plus 2x I-ISCH (20 dibits ea.) from the start of the fragment. + */ + private static final int DIBIT_DELAY_BUFFER_INDEX_SYNC_1 = 360 + FRAGMENT_BUFFER_OVERSIZE; + + /** + * S-ISCH Sync pattern 2 is 3x timeslots (160 dibits ea.) plus 3x I-ISCH (20 dibits ea.) from the start of the fragment. + */ + private static final int DIBIT_DELAY_BUFFER_INDEX_SYNC_2 = 540 + FRAGMENT_BUFFER_OVERSIZE; + + /** + * The maximum sync match error threshold when in a synchronized state is 7 in order to detect a stuffed (ie extra) + * dibit or a lost dibit that causes the stream to shift left or right. The hamming distance for the sync pattern + * shifted left or right by 2 bit positions (ie 1 dibit) is 8 (left) or 9 (right). So we set the threshold to + * just below 8. + */ + private static final int SYNCHRONIZED_SYNC_MATCH_THRESHOLD = 7; + + /** + * Use a lower threshold for sync reacquisition. + */ private static final int UN_SYNCHRONIZED_SYNC_MATCH_THRESHOLD = 4; private ScramblingSequence mScramblingSequence = new ScramblingSequence(); private Listener mMessageListener; private P25P2SyncDetector mSyncDetector; - private DibitDelayBuffer mSyncDetectionDelayBuffer = new DibitDelayBuffer(160); - private DibitDelayBuffer mFragmentBuffer = new DibitDelayBuffer(720); + /** + * The sync detection delay buffer is sized per the 160 dibits (320 bits) distance between the end of the fragment + * and the end of the second sync pattern (160) plus any extra dibits to account for the oversized fragment buffer. + */ + private DibitDelayBuffer mSyncDetectionDelayBuffer = new DibitDelayBuffer(160 + FRAGMENT_BUFFER_OVERSIZE); + + /** + * The fragment buffer is sized to hold a complete super frame fragment (720 dibits) plus two extra preceding and + * succeeding dibitsto support shifting the extracted super frame left or right 1/2 places when we detect 1 or 2 + * stuffed or deleted dibits in the sequence. + */ + private DibitDelayBuffer mFragmentBuffer = new DibitDelayBuffer(720 + (2 * FRAGMENT_BUFFER_OVERSIZE)); private int mDibitsProcessed = 0; private boolean mSynchronized = false; private ISyncDetectListener mSyncDetectListener; @@ -173,50 +221,99 @@ public void receive(Dibit dibit) * a registered listener. * * @param bitErrors detected for first and second ISCH-S segments combined + * @param dibitOffset to shift the extraction left (-) or right (+) by one or two dibits when dibit stuffing or + * deletion are detected. */ - private void broadcastFragment(int bitErrors) + private void broadcastFragment(int bitErrors, int dibitOffset) { - if(mDibitsProcessed > FRAGMENT_DIBIT_LENGTH) + if((mDibitsProcessed + dibitOffset) > FRAGMENT_DIBIT_LENGTH) { - broadcastSyncLoss(mDibitsProcessed - FRAGMENT_DIBIT_LENGTH); + broadcastSyncLoss(mDibitsProcessed + dibitOffset - FRAGMENT_DIBIT_LENGTH); } - mDibitsProcessed = 0; - CorrectedBinaryMessage message = mFragmentBuffer.getMessage(0, 720); + mDibitsProcessed = 0 + dibitOffset; + CorrectedBinaryMessage message = mFragmentBuffer.getMessage(FRAGMENT_BUFFER_OVERSIZE + dibitOffset, 720); message.setCorrectedBitCount(bitErrors); SuperFrameFragment frameFragment = new SuperFrameFragment(message, getCurrentTimestamp(), mScramblingSequence); + updateScramblingCode(frameFragment); + broadcast(frameFragment); + } + + /** + * Creates a super-frame fragment from the current contents of the fragment dibit buffer and broadcasts it to + * a registered listener. This method supports the use case when sync pattern 1 can be aligned by a small offset + * and sync pattern 2 can also be aligned by a small offset, however there is also an offset between sync 1 and + * sync 2 within the current stream, so we have to 'Frankenstein' together a message from the two fragments where + * the first half is aligned to sync 1 and the second half is aligned to sync 2. This possibly happens when the + * dibit stuffing or deletion occurs between the two sync patterns in the stream and will likely mean that timeslot + * 3 is corrupted, but we're (potentially) salvaging timeslots 1, 2 and 4 of the fragment. + * + * @param bitErrors detected for first and second ISCH-S segments combined + * @param sync1Offset to align the first part of the message with sync pattern 1. + * @param sync2Offset to align the second part of the message with sync patter 2. + */ + private void broadcastSplitFragment(int bitErrors, int sync1Offset, int sync2Offset) + { + if((mDibitsProcessed) > FRAGMENT_DIBIT_LENGTH) + { + broadcastSyncLoss(mDibitsProcessed + FRAGMENT_DIBIT_LENGTH); + } + mDibitsProcessed = 0 + sync2Offset; //We're only concerned with adjusting for sync 2 offset from here on out. + CorrectedBinaryMessage message1 = mFragmentBuffer.getMessage(FRAGMENT_BUFFER_OVERSIZE + sync1Offset, 720); + //Clear the bits from sync 2 start bit index 1080 (dibit 540) inclusive through bit index 1440 (exclusive). + message1.clear(1080, 1440); + CorrectedBinaryMessage message2 = mFragmentBuffer.getMessage(FRAGMENT_BUFFER_OVERSIZE + sync2Offset, 720); + //Clear the bits from bit index 0 (inclusive) through bit index 1080 (dibit 540) exclusive + message2.clear(0, 1080); + //Xor the two messages to produce a unified super frame fragment + message1.xor(message2); + message1.setCorrectedBitCount(bitErrors); //Not even sure what the correct bit error count is here? + SuperFrameFragment frameFragment = new SuperFrameFragment(message1, getCurrentTimestamp(), mScramblingSequence); updateScramblingCode(frameFragment); broadcast(frameFragment); } + /** + * Updates the scrambling code when we receive a network status broadcast with WACN, SYSTEM and NAC. + * @param superFrameFragment + */ private void updateScramblingCode(SuperFrameFragment superFrameFragment) { + boolean updated = false; + for(Timeslot timeslot: superFrameFragment.getTimeslots()) { - if(timeslot instanceof AbstractSignalingTimeslot) + if(timeslot instanceof AbstractSignalingTimeslot abstractSignalingTimeslot) { - List macMessages = ((AbstractSignalingTimeslot)timeslot).getMacMessages(); + List macMessages = abstractSignalingTimeslot.getMacMessages(); for(MacMessage macMessage: macMessages) { - MacOpcode macOpcode = macMessage.getMacStructure().getOpcode(); - - if(macOpcode == MacOpcode.PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED && - macMessage.getMacStructure() instanceof NetworkStatusBroadcastAbbreviated) + if(macMessage.isValid()) { - NetworkStatusBroadcastAbbreviated networkStatus = (NetworkStatusBroadcastAbbreviated)macMessage.getMacStructure(); - mScramblingSequence.update(networkStatus.getScrambleParameters()); - } - else if(macOpcode == MacOpcode.PHASE1_251_NETWORK_STATUS_BROADCAST_EXTENDED && - macMessage.getMacStructure() instanceof NetworkStatusBroadcastExtended) - { - NetworkStatusBroadcastExtended networkStatus = (NetworkStatusBroadcastExtended)macMessage.getMacStructure(); - mScramblingSequence.update(networkStatus.getScrambleParameters()); + MacOpcode macOpcode = macMessage.getMacStructure().getOpcode(); + + if(macOpcode == MacOpcode.PHASE1_7B_NETWORK_STATUS_BROADCAST_IMPLICIT && + macMessage.getMacStructure() instanceof NetworkStatusBroadcastImplicit nsba) + { + updated |= mScramblingSequence.update(nsba.getScrambleParameters()); + } + else if(macOpcode == MacOpcode.PHASE1_FB_NETWORK_STATUS_BROADCAST_EXPLICIT && + macMessage.getMacStructure() instanceof NetworkStatusBroadcastExplicit nsbe) + { + updated |= mScramblingSequence.update(nsbe.getScrambleParameters()); + } } } } } + + //If we updated the scramble sequence, nullify the timeslots so they can be recreated descrambled. + if(updated) + { + superFrameFragment.resetTimeslots(); + } } private void broadcastSyncLoss(int dibitsProcessed) @@ -232,7 +329,10 @@ private void broadcast(IMessage message) } } - + /** + * Checks the current buffered fragment to detect sync pattern 1 and sync pattern 2 and broadcast the fragment. + * @param syncDetectorBitErrorCount number of bit errors in the detected sync pattern. + */ private void checkFragmentSync(int syncDetectorBitErrorCount) { //Since we're using multi-sync detection, only proceed if we've processed enough dibits. The first sync detector @@ -242,28 +342,72 @@ private void checkFragmentSync(int syncDetectorBitErrorCount) if(mSynchronized) { //If we're synchronized, then this is a counter based trigger and we check both sync locations - Dibit[] sync1Dibits = mFragmentBuffer.getBuffer(DIBIT_DELAY_BUFFER_INDEX_SYNC_1, 20); - int sync1BitErrorCount = P25P2SyncPattern.getBitErrorCount(sync1Dibits); + int sync1BitErrorCount = getSyncBitErrorCount(DIBIT_DELAY_BUFFER_INDEX_SYNC_1); if(sync1BitErrorCount <= SYNCHRONIZED_SYNC_MATCH_THRESHOLD) { - Dibit[] sync2Dibits = mFragmentBuffer.getBuffer(DIBIT_DELAY_BUFFER_INDEX_SYNC_2, 20); - int sync2BitErrorCount = P25P2SyncPattern.getBitErrorCount(sync2Dibits); + int sync2BitErrorCount = getSyncBitErrorCount(DIBIT_DELAY_BUFFER_INDEX_SYNC_2); if(sync2BitErrorCount <= SYNCHRONIZED_SYNC_MATCH_THRESHOLD) { - broadcastFragment(sync1BitErrorCount + sync2BitErrorCount); - syncDetected(sync1BitErrorCount + sync2BitErrorCount); + broadcastFragment(sync1BitErrorCount + sync2BitErrorCount, 0); return; } else { + //We are synchronized on sync 1 but not on sync 2. Check to see if we have a good sync 2 + //pattern by shifting left or right by one or two dibits to detect dibit stuffing/deletion + // between sync 1 and sync 2. + int sync2Offset = getSynchronizedSyncOffset(DIBIT_DELAY_BUFFER_INDEX_SYNC_2); + + if(isValidSyncOffset(sync2Offset)) + { + //Recalculate the sync 2 bit error count using the offset value. + sync2BitErrorCount = getSyncBitErrorCount(DIBIT_DELAY_BUFFER_INDEX_SYNC_2 + sync2Offset); + broadcastSplitFragment(sync1BitErrorCount + sync2BitErrorCount, 0, sync2Offset); + } + + //Since we're getting misaligned, set unsynchronized to re-enter active sync inspection mSynchronized = false; return; } } else { + //We are synchronized but we've lost sync on current sync 1. Check to see if we have a good sync + //pattern by shifting left or right by one or two dibits to detect dibit stuffing/deletion. + int sync1Offset = getSynchronizedSyncOffset(DIBIT_DELAY_BUFFER_INDEX_SYNC_1); + + if(isValidSyncOffset(sync1Offset)) + { + //Update the sync 1 bit error count, calculated using the new offset. + sync1BitErrorCount = getSyncBitErrorCount(DIBIT_DELAY_BUFFER_INDEX_SYNC_1 + sync1Offset); + int sync2BitErrorCount = getSyncBitErrorCount(DIBIT_DELAY_BUFFER_INDEX_SYNC_2 + sync1Offset); + + if(sync2BitErrorCount <= SYNCHRONIZED_SYNC_MATCH_THRESHOLD) + { + //Broadcast the fragment using just the sync 1 offset. + broadcastFragment(sync1BitErrorCount + sync2BitErrorCount, sync1Offset); + } + else + { + //Check to see if there's a different offset for sync 2, relative to the new sync 1 offset + int sync2Offset = getSynchronizedSyncOffset(DIBIT_DELAY_BUFFER_INDEX_SYNC_2 + sync1Offset); + + if(isValidSyncOffset(sync2Offset)) + { + int totalOffset = sync1Offset + sync2Offset; + + //Don't allow the total (sync 1 + sync2) offset to exceed the range (-2 to +2) + if(isValidSyncOffset(totalOffset)) + { + broadcastSplitFragment(sync1BitErrorCount + sync2BitErrorCount, sync1Offset, sync2Offset); + } + } + } + } + + //Since we're getting misaligned, set unsynchronized to re-enter active sync inspection mSynchronized = false; return; } @@ -271,13 +415,12 @@ private void checkFragmentSync(int syncDetectorBitErrorCount) //If we're not synchronized, this is a sync detector trigger and we only have to check sync 1 for error // count because the sync detector has already triggered on sync 2 - Dibit[] sync1Dibits = mFragmentBuffer.getBuffer(DIBIT_DELAY_BUFFER_INDEX_SYNC_1, 20); - int sync1BitErrorCount = P25P2SyncPattern.getBitErrorCount(sync1Dibits); + int sync1BitErrorCount = getSyncBitErrorCount(DIBIT_DELAY_BUFFER_INDEX_SYNC_1); if(sync1BitErrorCount <= UN_SYNCHRONIZED_SYNC_MATCH_THRESHOLD) { mSynchronized = true; - broadcastFragment(sync1BitErrorCount + syncDetectorBitErrorCount); + broadcastFragment(sync1BitErrorCount + syncDetectorBitErrorCount, 0); } else { @@ -295,4 +438,56 @@ private void checkFragmentSync(int syncDetectorBitErrorCount) } } } + + /** + * Indicates if the sync offset returned from the getSyncOffset() method is valid. + * @param offset to test + * @return true if the offset falls in the range: (-2 to 2) + */ + private boolean isValidSyncOffset(int offset) + { + return -2 <= offset && offset <= 2; + } + + /** + * Attempts to identify a positive or negative dibit offset value that would allow a valid sync pattern match + * and stream alignment. Looks to the left and right of the specified index by 1 or 2 dibits of offset to + * determine if the dibit sequence at that location has a bit error count lower than the synchronized threshold + * and reduces that threshold accordingly as we shift left/right by 1/2 dibit places. + * @param index for a normal sync detection. + * @return offset in the range of (-2 to +2) or Integer.MIN_VALUE if none of the tested offsets would produce sync. + */ + private int getSynchronizedSyncOffset(int index) + { + if(getSyncBitErrorCount(index - 1) <= SYNCHRONIZED_SYNC_MATCH_THRESHOLD - 1) + { + return -1; + } + else if(getSyncBitErrorCount(index + 1) <= SYNCHRONIZED_SYNC_MATCH_THRESHOLD - 1) + { + return 1; + } + else if(getSyncBitErrorCount(index - 2) <= SYNCHRONIZED_SYNC_MATCH_THRESHOLD - 2) + { + return -2; + } + else if(getSyncBitErrorCount(index + 2) <= SYNCHRONIZED_SYNC_MATCH_THRESHOLD - 2) + { + return 2; + } + + return Integer.MIN_VALUE; + } + + /** + * Calculates the bit error count for the sequence of 20-dibits starting at the specified index against the + * normal P25 Phase 2 sync pattern. + * @param index for the start of the 20-dibit sequence. + * @return bit error count (ie hamming distance) of the 20-dibit sequence compared to the sync pattern. + */ + private int getSyncBitErrorCount(int index) + { + Dibit[] dibits = mFragmentBuffer.getBuffer(index, 20); + return P25P2SyncPattern.getBitErrorCount(dibits); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2SyncDetector.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2SyncDetector.java index d609d9c44..20f6a3ea8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2SyncDetector.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2SyncDetector.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2; @@ -68,19 +65,19 @@ public P25P2SyncDetector(ISyncDetectListener syncDetectListener, IPhaseLockedLoo if(phaseLockedLoop != null) { - //Add additional sync pattern detectors to detect when we get 90/180 degree out of phase sync pattern - //detections so that we can apply correction to the phase locked loop - mInversionDetector90CW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE2_ERROR_90_CW, - phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_90_DEGREES); - mMatcher.add(mInversionDetector90CW); - - mInversionDetector90CCW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE2_ERROR_90_CCW, - phaseLockedLoop, DEFAULT_SAMPLE_RATE, -FREQUENCY_PHASE_CORRECTION_90_DEGREES); - mMatcher.add(mInversionDetector90CCW); - - mInversionDetector180 = new PLLPhaseInversionDetector(FrameSync.P25_PHASE2_ERROR_180, - phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_180_DEGREES); - mMatcher.add(mInversionDetector180); +// //Add additional sync pattern detectors to detect when we get 90/180 degree out of phase sync pattern +// //detections so that we can apply correction to the phase locked loop +// mInversionDetector90CW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE2_ERROR_90_CW, +// phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_90_DEGREES); +// mMatcher.add(mInversionDetector90CW); +// +// mInversionDetector90CCW = new PLLPhaseInversionDetector(FrameSync.P25_PHASE2_ERROR_90_CCW, +// phaseLockedLoop, DEFAULT_SAMPLE_RATE, -FREQUENCY_PHASE_CORRECTION_90_DEGREES); +// mMatcher.add(mInversionDetector90CCW); +// +// mInversionDetector180 = new PLLPhaseInversionDetector(FrameSync.P25_PHASE2_ERROR_180, +// phaseLockedLoop, DEFAULT_SAMPLE_RATE, FREQUENCY_PHASE_CORRECTION_180_DEGREES); +// mMatcher.add(mInversionDetector180); } } @@ -107,9 +104,9 @@ public void receive(Dibit dibit) */ public void setSampleRate(double sampleRate) { - mInversionDetector180.setSampleRate(sampleRate); - mInversionDetector90CW.setSampleRate(sampleRate); - mInversionDetector90CCW.setSampleRate(sampleRate); +// mInversionDetector180.setSampleRate(sampleRate); +// mInversionDetector90CW.setSampleRate(sampleRate); +// mInversionDetector90CCW.setSampleRate(sampleRate); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/DataUnitID.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/DataUnitID.java index 2cfd5a796..fa56f324a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/DataUnitID.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/DataUnitID.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.enumeration; @@ -30,12 +27,21 @@ public enum DataUnitID { VOICE_4(0, 0x00,"VOICE-4"), + RESERVED_1(1, 0x17, "RESERVED 1"), + RESERVED_2(2, 0x2E, "RESERVED 2"), SCRAMBLED_SACCH(3, 0x39, "SACCH-S"), + RESERVED_4(4, 0x4B, "RESERVED 4"), + RESERVED_5(5, 0x5C, "RESERVED 5"), VOICE_2(6, 0x65, "VOICE-2"), + RESERVED_7(7, 0x72, "RESERVED 7"), + RESERVED_8(8, 0x8D, "RESERVED 8"), SCRAMBLED_FACCH(9, 0x9A, "FACCH-S"), + RESERVED_A(10, 0xA3, "RESERVED 10"), + RESERVED_B(11, 0xB4, "RESERVED 11"), UNSCRAMBLED_SACCH(12, 0xC6, "SACCH-U"), + UNSCRAMBLED_LCCH(13, 0xD1, "LOCCH-U"), + RESERVED_E(14, 0xE8, "RESERVED 14"), UNSCRAMBLED_FACCH(15, 0xFF, "FACCH-U"), - UNKNOWN(-1, 0x00, "UNKNOWN-"); private int mValue; @@ -81,6 +87,15 @@ public boolean isFACCH() return this == SCRAMBLED_FACCH || this == UNSCRAMBLED_FACCH; } + /** + * Indicates if this DUID is a LCCH + * @return + */ + public boolean isLCCH() + { + return this == UNSCRAMBLED_LCCH; + } + @Override public String toString() { @@ -95,26 +110,44 @@ public String toString() */ public static DataUnitID fromEncodedValue(int value) { - int masked = 0xF & (value >> 4); - - switch(masked) + switch(value) { - case 0: + case 0x00: return VOICE_4; - case 3: + case 0x17: + return RESERVED_1; + case 0x2E: + return RESERVED_2; + case 0x39: return SCRAMBLED_SACCH; - case 6: + case 0x4B: + return RESERVED_4; + case 0x5C: + return RESERVED_5; + case 0x65: return VOICE_2; - case 9: + case 0x72: + return RESERVED_7; + case 0x8D: + return RESERVED_8; + case 0x9A: return SCRAMBLED_FACCH; - case 12: + case 0xA3: + return RESERVED_A; + case 0xB4: + return RESERVED_B; + case 0xC6: return UNSCRAMBLED_SACCH; - case 15: + case 0xD1: + return UNSCRAMBLED_LCCH; + case 0xE8: + return RESERVED_E; + case 0xFF: return UNSCRAMBLED_FACCH; } DataUnitID closest = DataUnitID.UNKNOWN; - int errorCount = 4; + int errorCount = 8; for(DataUnitID duid: VALID_VALUES) { @@ -128,6 +161,12 @@ public static DataUnitID fromEncodedValue(int value) } } - return closest; + //The encoding (8,4,4) should be able to detect up to 2 bit errors. Anything more is ambiguous. + if(errorCount <= 2) + { + return closest; + } + + return UNKNOWN; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/ISCHSequence.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/ISCHSequence.java index 2cfb1ed7b..57ed8c188 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/ISCHSequence.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/ISCHSequence.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.enumeration; @@ -26,9 +23,9 @@ */ public enum ISCHSequence { - ISCH_1(0, "FRAG1", 0), - ISCH_2(1, "FRAG2", 4), - ISCH_3(2, "FRAG3", 8), + ISCH_1(0, "FRAGMENT 1/3", 0), + ISCH_2(1, "FRAGMENT 2/3", 4), + ISCH_3(2, "FRAGMENT 3/3", 8), RESERVED_4(3, "RSVD4", 0), UNKNOWN(-1, "UNKNO", 0); @@ -52,7 +49,7 @@ public int getValue() } /** - * Offset to apply to the each of the timeslots in a fragment. + * Offset to apply to each of the timeslots in a fragment. * @return timeslot offset */ public int getTimeslotOffset() diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/LCHType.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/LCHType.java new file mode 100644 index 000000000..20339ec86 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/LCHType.java @@ -0,0 +1,89 @@ +/* + * ***************************************************************************** + * 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.phase2.enumeration; + +/** + * LCH timeslot configuration type enumeration used in the I-ISCH. + */ +public enum LCHType +{ + VCH("VCH", "INBOUND SACCH IN USE", "INBOUND SACCH FREE"), + DCH("DCH", "DATA 0", "DATA 1"), + RESERVED("RESERVED", "RESERVED 0", "RESERVED 1"), + LCCH("LCCH", "SINGLE-SLOT CONTROL", "DUAL-SLOT CONTROL"), + UNKNOWN("UNKNOWN", "UNKNOWN 0", "UNKNOWN 1"); + + private String mLabel; + private String mLchFlagFalse; + private String mLchFlagTrue; + + /** + * Constructs an instance + * @param label to use for the entry + * @param lchFlagFalse label for when the LCH flag is false value. + * @param lchFlagTrue label for when the LCH flag is true value. + */ + LCHType(String label, String lchFlagFalse, String lchFlagTrue) + { + mLabel = label; + mLchFlagFalse = lchFlagFalse; + mLchFlagTrue = lchFlagTrue; + } + + + + /** + * Utility method to lookup LCH Type from a numeric value. + * @param value to lookup + * @return LCH type or UNKNOWN + */ + public static LCHType fromValue(int value) + { + switch(value) + { + case 0: + return VCH; + case 1: + return DCH; + case 2: + return RESERVED; + case 3: + return LCCH; + default: + return UNKNOWN; + } + } + + /** + * Gets the LCH flag meaning for this LCH type. + * @param flag value. + * @return label for the flag value for this LCH type. + */ + public String getLCHFlagLabel(boolean flag) + { + return flag ? mLchFlagTrue : mLchFlagFalse; + } + + @Override + public String toString() + { + return mLabel; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/SuperframeSequence.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/SuperframeSequence.java index 4ba72fd2f..3c1193afd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/SuperframeSequence.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/enumeration/SuperframeSequence.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.enumeration; @@ -26,10 +23,10 @@ */ public enum SuperframeSequence { - SUPERFRAME_1(0, "SF1"), - SUPERFRAME_2(1, "SF2"), - SUPERFRAME_3(2, "SF3"), - SUPERFRAME_4(3, "SF4"), + SUPERFRAME_1(0, "SUPERFRAME 1/4"), + SUPERFRAME_2(1, "SUPERFRAME 2/4"), + SUPERFRAME_3(2, "SUPERFRAME 3/4"), + SUPERFRAME_4(3, "SUPERFRAME 4/4"), UNKNOWN(-1, "UNK"); private int mValue; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/EncryptionSynchronizationSequence.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/EncryptionSynchronizationSequence.java index 4308e95fb..017e5cc15 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/EncryptionSynchronizationSequence.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/EncryptionSynchronizationSequence.java @@ -1,34 +1,31 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message; import io.github.dsheirer.audio.codec.mbe.IEncryptionSyncParameters; -import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier; import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey; - -import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -37,34 +34,30 @@ */ public class EncryptionSynchronizationSequence extends P25P2Message implements IEncryptionSyncParameters { - private static final int[] ALGORITHM = new int[]{0, 1, 2, 3, 4, 5, 6, 7}; - private static final int[] KEY_ID = new int[]{8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] MESSAGE_INDICATOR_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] MESSAGE_INDICATOR_2 = new int[]{32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] MESSAGE_INDICATOR_3 = new int[]{40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] MESSAGE_INDICATOR_4 = new int[]{48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] MESSAGE_INDICATOR_5 = new int[]{56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] MESSAGE_INDICATOR_6 = new int[]{64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] MESSAGE_INDICATOR_7 = new int[]{72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] MESSAGE_INDICATOR_8 = new int[]{80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] MESSAGE_INDICATOR_9 = new int[]{88, 89, 90, 91, 92, 93, 94, 95}; + private static final IntField ALGORITHM = IntField.length8(0); + private static final IntField KEY_ID = IntField.length16(8); + private static final IntField MESSAGE_INDICATOR_1 = IntField.length8(24); + private static final IntField MESSAGE_INDICATOR_2 = IntField.length8(32); + private static final IntField MESSAGE_INDICATOR_3 = IntField.length8(40); + private static final IntField MESSAGE_INDICATOR_4 = IntField.length8(48); + private static final IntField MESSAGE_INDICATOR_5 = IntField.length8(56); + private static final IntField MESSAGE_INDICATOR_6 = IntField.length8(64); + private static final IntField MESSAGE_INDICATOR_7 = IntField.length8(72); + private static final IntField MESSAGE_INDICATOR_8 = IntField.length8(80); + private static final IntField MESSAGE_INDICATOR_9 = IntField.length8(88); - private BinaryMessage mMessage; private EncryptionKeyIdentifier mEncryptionKey; - private int mTimeslot; - public EncryptionSynchronizationSequence(BinaryMessage message, int timeslot, long timestamp) + public EncryptionSynchronizationSequence(CorrectedBinaryMessage message, int timeslot, long timestamp) { - super(timestamp); - mMessage = message; - mTimeslot = timeslot; + super(message, 0, timeslot, timestamp); } @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("TS").append(mTimeslot); + sb.append("TS").append(getTimeslot()); sb.append(" ESS ").append(getEncryptionKey().toString()); if(isEncrypted()) @@ -98,7 +91,7 @@ public EncryptionKeyIdentifier getEncryptionKey() */ public int getAlgorithmId() { - return mMessage.getInt(ALGORITHM); + return getMessage().getInt(ALGORITHM); } /** @@ -114,7 +107,7 @@ public boolean isEncrypted() */ public int getEncryptionKeyId() { - return mMessage.getInt(KEY_ID); + return getMessage().getInt(KEY_ID); } /** @@ -124,35 +117,21 @@ public int getEncryptionKeyId() public String getMessageIndicator() { StringBuilder sb = new StringBuilder(); - sb.append(mMessage.getHex(MESSAGE_INDICATOR_1, 2).toUpperCase()); - sb.append(mMessage.getHex(MESSAGE_INDICATOR_2, 2).toUpperCase()); - sb.append(mMessage.getHex(MESSAGE_INDICATOR_3, 2).toUpperCase()); - sb.append(mMessage.getHex(MESSAGE_INDICATOR_4, 2).toUpperCase()); - sb.append(mMessage.getHex(MESSAGE_INDICATOR_5, 2).toUpperCase()); - sb.append(mMessage.getHex(MESSAGE_INDICATOR_6, 2).toUpperCase()); - sb.append(mMessage.getHex(MESSAGE_INDICATOR_7, 2).toUpperCase()); - sb.append(mMessage.getHex(MESSAGE_INDICATOR_8, 2).toUpperCase()); - sb.append(mMessage.getHex(MESSAGE_INDICATOR_9, 2).toUpperCase()); + sb.append(getIntAsHex(MESSAGE_INDICATOR_1, 2)); + sb.append(getIntAsHex(MESSAGE_INDICATOR_2, 2)); + sb.append(getIntAsHex(MESSAGE_INDICATOR_3, 2)); + sb.append(getIntAsHex(MESSAGE_INDICATOR_4, 2)); + sb.append(getIntAsHex(MESSAGE_INDICATOR_5, 2)); + sb.append(getIntAsHex(MESSAGE_INDICATOR_6, 2)); + sb.append(getIntAsHex(MESSAGE_INDICATOR_7, 2)); + sb.append(getIntAsHex(MESSAGE_INDICATOR_8, 2)); + sb.append(getIntAsHex(MESSAGE_INDICATOR_9, 2)); return sb.toString(); } - @Override - public boolean isValid() - { - return true; - } - - @Override - public int getTimeslot() - { - return mTimeslot; - } - @Override public List getIdentifiers() { - List identifiers = new ArrayList<>(); - identifiers.add(getEncryptionKey()); - return identifiers; + return Collections.singletonList(getEncryptionKey()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/EncryptionSynchronizationSequenceProcessor.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/EncryptionSynchronizationSequenceProcessor.java index cf509c52f..6764206b7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/EncryptionSynchronizationSequenceProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/EncryptionSynchronizationSequenceProcessor.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.ReedSolomon_44_16_29_P25; import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot; import io.github.dsheirer.module.decode.p25.phase2.timeslot.Voice2Timeslot; @@ -184,7 +182,7 @@ public EncryptionSynchronizationSequence getSequence() if(!irrecoverableErrors) { //Transfer error corrected output to a new binary message - BinaryMessage message = new BinaryMessage(96); + CorrectedBinaryMessage message = new CorrectedBinaryMessage(96); int pointer = 0; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/InterSlotSignallingChannel.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/InterSlotSignallingChannel.java deleted file mode 100644 index 64f79131f..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/InterSlotSignallingChannel.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.module.decode.p25.phase2.enumeration.ISCHSequence; -import io.github.dsheirer.module.decode.p25.phase2.enumeration.SuperframeSequence; -import org.apache.commons.math3.linear.MatrixUtils; -import org.apache.commons.math3.linear.RealMatrix; -import org.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; -import java.util.TreeMap; - -/** - * Inter-slot signalling channel informational (ISCH-I) parsing class - */ -public class InterSlotSignallingChannel -{ - private final static Logger mLog = LoggerFactory.getLogger(InterSlotSignallingChannel.class); - - private static Map sCodewordMap = new TreeMap<>(); - - static - { - double[][] matrix = {{1,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0,1,1,0,0,1,1,1,0,0,0,1,1,0,1,1,0,1,1,0,1,0,1,1,1}, - {0,0,1,0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,0,0,1,1,1,1,0,1,1,0,0,1,0,0}, - {0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,1,0,1,0,0,1,0,1,1,0,0,0,1,0,1,1,1,0,1,0,1,1,0,0,0}, - {0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1,1,0,1,1,0,1,0,0,0,1,1,0,0,0,1,1,1,0}, - {0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1}, - {0,0,0,0,1,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,1,1,1,0,0,1,0}, - {0,0,0,0,0,0,0,0,1,0,0,1,1,1,0,1,1,0,1,0,0,0,1,1,1,0,1,0,0,0,0,1,0,1,1,1,0,0,0,1}, - {0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,1,1,0,0,1,0,1,1,1,0,1,0,1,0,1,0,0,1,0,0,1,1,1,0}, - {0,0,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,0,1,1,1,1,0,1,1,0,0,0,0,1,0,1,1,0,0,1,0,1,1,1}}; - - RealMatrix generator = MatrixUtils.createRealMatrix(matrix); - - //We only generate a lookup map for 7/10 least significant bits since MSB and reserved bits are not used - for(int x = 0; x < 128; x++) - { - RealMatrix word = toMatrix10(x); - RealMatrix codewordMatrix = word.multiply(generator); - long codeword = decodeMatrix(codewordMatrix); - codeword ^= 0x184229d461l; - BinaryMessage message = new BinaryMessage(9); - message.load(0, 9, x); - - sCodewordMap.put(codeword, message); - } - } - - private static final int[] RESERVED = {0, 1}; - private static final int[] CHANNEL_NUMBER = {2, 3}; - private static final int[] ISCH_SEQUENCE = {4, 5}; - private static final int INBOUND_SACCH_FREE_INDICATOR = 6; - private static final int[] SUPERFRAME_SEQUENCE = {7, 8}; - - private CorrectedBinaryMessage mMessage; - private boolean mValid; - - /** - * Constructs the ISCH-I parsing class - * - * @param message containing bits - * @param expectedTimeslot for this ISCH, either channel 0 or channel 1 to assist with validating the message - */ - public InterSlotSignallingChannel(BinaryMessage message, int expectedTimeslot) - { - decode(message); - mValid = (getMessage().getCorrectedBitCount() < 9) && (getTimeslot() == expectedTimeslot); - } - - /** - * Decodes the 40-bit message codeword into an error-corrected 9-bit message - * @param message containing 40 bit codeword - */ - private void decode(BinaryMessage message) - { - long codeword = message.getLong(0, 39); - - if(sCodewordMap.containsKey(codeword)) - { - mMessage = new CorrectedBinaryMessage(sCodewordMap.get(codeword)); - } - else - { - int smallestErrorCount = 16; - long closestCodeword = 0; - - for(long validCodeword: sCodewordMap.keySet()) - { - long mask = codeword & validCodeword; - int errorCount = Long.bitCount(mask); - - if(errorCount < smallestErrorCount) - { - smallestErrorCount = errorCount; - closestCodeword = validCodeword; - } - } - - if(closestCodeword != 0) - { - mMessage = new CorrectedBinaryMessage(sCodewordMap.get(closestCodeword)); - mMessage.setCorrectedBitCount(smallestErrorCount); - } - else - { - //This shouldn't happen, but we'll set bit error count to 9 to indicate a bad decode - mMessage = new CorrectedBinaryMessage(9); - mMessage.setCorrectedBitCount(9); - } - } - } - - /** - * Indicates if this message is valid - */ - public boolean isValid() - { - return mValid; - } - - /** - * Bit error count or the number of bits that were corrected while decoding the transmitted 40-bit codeword - */ - public int getBitErrorCount() - { - return getMessage().getCorrectedBitCount(); - } - - /** - * Decoded and corrected 9-bit message - */ - private CorrectedBinaryMessage getMessage() - { - return mMessage; - } - - /** - * Timeslot for this ISCH - * - * @return timeslot 0 or 1 - */ - public int getTimeslot() - { - return getMessage().getInt(CHANNEL_NUMBER); - } - - /** - * Indicates this ISCH sequence's location within a super-frame - * - * @return location 1, 2, or 3(final) - */ - public ISCHSequence getIschSequence() - { - return ISCHSequence.fromValue(getMessage().getInt(ISCH_SEQUENCE)); - } - - /** - * Indicates if the next inbound SACCH timeslot is free for mobile access - * - * @return true if the inbound SACCH is free - */ - public boolean isInboundSacchFree() - { - return getMessage().get(INBOUND_SACCH_FREE_INDICATOR); - } - - /** - * Superframe sequence/location within an ultraframe - * - * @return location, 1-4 - */ - public SuperframeSequence getSuperframeSequence() - { - return SuperframeSequence.fromValue(getMessage().getInt(SUPERFRAME_SEQUENCE)); - } - - /** - * Decoded string representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - - if(isValid()) - { - sb.append("ISCHI ").append(getTimeslot()); - sb.append(" ").append(getIschSequence()); - sb.append(" ").append(getSuperframeSequence()); - sb.append(isInboundSacchFree() ? " SACCH:FREE" : " SACCH:BUSY"); - } - else - { - sb.append("ISCHI **INVALID** "); - } - - return sb.toString(); - } - - /** - * Creates a 10-element matrix from the value. - * @param value in range 0 - 127 - * @return matrix - */ - private static RealMatrix toMatrix10(int value) - { - double[] values = new double[9]; - for(int x = 0; x < 7; x++) - { - int mask = (int) FastMath.pow(2, x); - if((value & mask) == mask) - { - values[8 - x] = 1; - } - } - - return MatrixUtils.createRowRealMatrix(values); - } - - /** - * Decodes the matrix which is assumed to be a single row with 40 elements representing bits - * @param matrix to decode - * @return long value - */ - private static long decodeMatrix(RealMatrix matrix) - { - long decoded = 0; - double[] values = matrix.getRow(0); - - for(int x = 0; x < 40; x++) - { - int value = (int)values[39 - x]; - - if((value & 1) == 1) - { - decoded += (long)FastMath.pow(2, x); - } - } - - return decoded; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/P25P2Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/P25P2Message.java index 4637062e0..65fa2bc4c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/P25P2Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/P25P2Message.java @@ -1,72 +1,41 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message; +import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.message.TimeslotMessage; import io.github.dsheirer.protocol.Protocol; /** * APCO25 Phase 2 Base Message */ -public abstract class P25P2Message implements IMessage +public abstract class P25P2Message extends TimeslotMessage implements IMessage { - private long mTimestamp; - private boolean mValid = true; - /** * Constructs the message * @param timestamp of the final bit of the message */ - protected P25P2Message(long timestamp) - { - mTimestamp = timestamp; - } - - /** - * Timestamp when the final bit of this message was transmitted - * @return timestamp as milliseconds since epoch - */ - @Override - public long getTimestamp() - { - return mTimestamp; - } - - /** - * Indicates if this message is valid - */ - @Override - public boolean isValid() + protected P25P2Message(CorrectedBinaryMessage message, int offset, int timeslot, long timestamp) { - return mValid; - } - - /** - * Sets the valid flag for this message - */ - public void setValid(boolean valid) - { - mValid = valid; + super(message, offset, timeslot, timestamp); } /** @@ -77,10 +46,4 @@ public Protocol getProtocol() { return Protocol.APCO25_PHASE2; } - - /** - * Indicates the timeslot, 0 or 1, for this message - */ - @Override - public abstract int getTimeslot(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/SuperFrameFragment.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/SuperFrameFragment.java index f5e47d6de..5de679bd9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/SuperFrameFragment.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/SuperFrameFragment.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message; @@ -26,12 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.message.IMessage; -import io.github.dsheirer.module.decode.p25.phase2.enumeration.ISCHSequence; +import io.github.dsheirer.module.decode.p25.phase2.message.isch.IISCH; +import io.github.dsheirer.module.decode.p25.phase2.message.isch.ISCHDecoder; +import io.github.dsheirer.module.decode.p25.phase2.message.isch.SISCH; import io.github.dsheirer.module.decode.p25.phase2.timeslot.ScramblingSequence; import io.github.dsheirer.module.decode.p25.phase2.timeslot.Timeslot; import io.github.dsheirer.module.decode.p25.phase2.timeslot.TimeslotFactory; import io.github.dsheirer.protocol.Protocol; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -55,16 +53,21 @@ public class SuperFrameFragment implements IMessage private static final int TIMESLOT_D_START = 1120; private static final int TIMESLOT_D_END = 1440; + private static final ISCHDecoder ISCH_DECODER = new ISCHDecoder(); private long mTimestamp; private CorrectedBinaryMessage mMessage; - private InterSlotSignallingChannel mChannel0Isch; - private InterSlotSignallingChannel mChannel1Isch; + private IISCH mIISCH1; + private IISCH mIISCH2; + private SISCH mSISCH1; + private SISCH mSISCH2; private Timeslot mTimeslotA; private Timeslot mTimeslotB; private Timeslot mTimeslotC; private Timeslot mTimeslotD; + private List mTimeslots; private ScramblingSequence mScramblingSequence; + /** * Constructs a fragment from the message with the specified timestamp. * @@ -106,31 +109,76 @@ public long getTimestamp() } /** - * Channel 0 Inter-slot Signalling CHannel (ISCH) + * IISCH Timeslot 1 + */ + public IISCH getIISCH1() + { + if(mIISCH1 == null) + { + CorrectedBinaryMessage message = ISCH_DECODER.decode(getMessage() + .getSubMessage(CHANNEL_A_ISCH_START, TIMESLOT_A_START), P25P2Message.TIMESLOT_1); + mIISCH1 = new IISCH(message, 0, 1, getTimestamp()); + mIISCH1.setValid(message.getCorrectedBitCount() < 3); + } + + return mIISCH1; + } + + /** + * IISCH Timeslot 2 + */ + public IISCH getIISCH2() + { + if(mIISCH2 == null) + { + CorrectedBinaryMessage message = ISCH_DECODER.decode(getMessage() + .getSubMessage(CHANNEL_B_ISCH_START, TIMESLOT_B_START), P25P2Message.TIMESLOT_2); + mIISCH2 = new IISCH(message, 0, 2, getTimestamp()); + mIISCH2.setValid(message.getCorrectedBitCount() < 3); + } + + return mIISCH2; + } + + /** + * SISCH Timeslot 1 */ - public InterSlotSignallingChannel getIschChannel0() + public SISCH getSISCH1() { - if(mChannel0Isch == null) + if(mSISCH1 == null) { - mChannel0Isch = new InterSlotSignallingChannel( - getMessage().getSubMessage(CHANNEL_A_ISCH_START, TIMESLOT_A_START), 0); + CorrectedBinaryMessage message = getMessage().getSubMessage(CHANNEL_C_ISCH_START, TIMESLOT_C_START); + mSISCH1 = new SISCH(message, 0, 1, getTimestamp()); } - return mChannel0Isch; + return mSISCH1; } /** - * Channel 1 Inter-slot Signalling CHannel (ISCH) + * SISCH Timeslot 2 */ - public InterSlotSignallingChannel getIschChannel1() + public SISCH getSISCH2() { - if(mChannel1Isch == null) + if(mSISCH2 == null) { - mChannel1Isch = new InterSlotSignallingChannel( - getMessage().getSubMessage(CHANNEL_B_ISCH_START, TIMESLOT_B_START), 1); + CorrectedBinaryMessage message = getMessage().getSubMessage(CHANNEL_D_ISCH_START, TIMESLOT_D_START); + mSISCH2 = new SISCH(message, 0, 2, getTimestamp()); } - return mChannel1Isch; + return mSISCH2; + } + + /** + * Reset the timeslots when the scrambling code has been updated so that any scrambled timeslots can be correctly + * decoded. + */ + public void resetTimeslots() + { + mTimeslots = null; + mTimeslotA = null; + mTimeslotB = null; + mTimeslotC = null; + mTimeslotD = null; } /** @@ -140,7 +188,7 @@ public Timeslot getTimeslotA() { if(mTimeslotA == null) { - mTimeslotA = getTimeslot(TIMESLOT_A_START, CHANNEL_B_ISCH_START, 0, 0); + mTimeslotA = getTimeslot(TIMESLOT_A_START, CHANNEL_B_ISCH_START, 0, 1); } return mTimeslotA; @@ -153,7 +201,7 @@ public Timeslot getTimeslotB() { if(mTimeslotB == null) { - mTimeslotB = getTimeslot(TIMESLOT_B_START, CHANNEL_C_ISCH_START, 1, 1); + mTimeslotB = getTimeslot(TIMESLOT_B_START, CHANNEL_C_ISCH_START, 1, 2); } return mTimeslotB; @@ -166,8 +214,7 @@ public Timeslot getTimeslotC() { if(mTimeslotC == null) { - mTimeslotC = getTimeslot(TIMESLOT_C_START, CHANNEL_D_ISCH_START, 2, - isFinalFragment() ? 1 : 0); + mTimeslotC = getTimeslot(TIMESLOT_C_START, CHANNEL_D_ISCH_START, 2, isFinalFragment() ? 2 : 1); } return mTimeslotC; @@ -180,8 +227,7 @@ public Timeslot getTimeslotD() { if(mTimeslotD == null) { - mTimeslotD = getTimeslot(TIMESLOT_D_START, TIMESLOT_D_END, 3, - isFinalFragment() ? 0 : 1); + mTimeslotD = getTimeslot(TIMESLOT_D_START, TIMESLOT_D_END, 3, isFinalFragment() ? 1 : 2); } return mTimeslotD; @@ -189,28 +235,31 @@ public Timeslot getTimeslotD() public List getTimeslots() { - List timeslots = new ArrayList<>(); - timeslots.add(getTimeslotA()); - timeslots.add(getTimeslotB()); - - if(isFinalFragment()) - { - timeslots.add(getTimeslotD()); - timeslots.add(getTimeslotC()); - } - else + if(mTimeslots == null) { - timeslots.add(getTimeslotC()); - timeslots.add(getTimeslotD()); + mTimeslots = new ArrayList<>(); + mTimeslots.add(getTimeslotA()); + mTimeslots.add(getTimeslotB()); + + if(isFinalFragment()) + { + mTimeslots.add(getTimeslotD()); + mTimeslots.add(getTimeslotC()); + } + else + { + mTimeslots.add(getTimeslotC()); + mTimeslots.add(getTimeslotD()); + } } - return timeslots; + return mTimeslots; } /** - * Channel 0 timeslots + * Channel 1 timeslots */ - public List getChannel0Timeslots() + public List getChannel1Timeslots() { List timeslots = new ArrayList<>(); timeslots.add(getTimeslotA()); @@ -228,9 +277,9 @@ public List getChannel0Timeslots() } /** - * Channel 1 timeslots + * Channel 2 timeslots */ - public List getChannel1Timeslots() + public List getChannel2Timeslots() { List timeslots = new ArrayList<>(); @@ -268,17 +317,14 @@ private Timeslot getTimeslot(int start, int end, int index, int timeslot) */ private int getTimeslotOffset() { - ISCHSequence sequence0 = getIschChannel0().getIschSequence(); - - if(getIschChannel0().isValid()) + if(getIISCH1().isValid()) { - return sequence0.getTimeslotOffset(); + return getIISCH1().getIschSequence().getTimeslotOffset(); } - ISCHSequence sequence1 = getIschChannel1().getIschSequence(); - if(getIschChannel1().isValid()) + if(getIISCH1().isValid()) { - return sequence1.getTimeslotOffset(); + return getIISCH2().getIschSequence().getTimeslotOffset(); } return 0; @@ -290,17 +336,14 @@ private int getTimeslotOffset() */ private boolean isFinalFragment() { - ISCHSequence sequence0 = getIschChannel0().getIschSequence(); - - if(getIschChannel0().isValid()) + if(getIISCH1().isValid()) { - return sequence0.isFinalFragment(); + return getIISCH1().getIschSequence().isFinalFragment(); } - ISCHSequence sequence1 = getIschChannel1().getIschSequence(); - if(getIschChannel1().isValid()) + if(getIISCH2().isValid()) { - return sequence1.isFinalFragment(); + return getIISCH2().getIschSequence().isFinalFragment(); } //Can't determine, so higher probability is that it's not the final fragment @@ -341,14 +384,10 @@ public List getIdentifiers() public String toString() { StringBuilder sb = new StringBuilder(); - - sb.append(getIschChannel0()); - sb.append(" | ").append(getIschChannel1()); - sb.append(" | Channel 0:").append(getChannel0Timeslots()); + sb.append(getIISCH1()); + sb.append(" | ").append(getIISCH2()); sb.append(" | Channel 1:").append(getChannel1Timeslots()); - + sb.append(" | Channel 2:").append(getChannel2Timeslots()); return sb.toString(); -// return getMessage().toHexString() + " SYNC BIT ERRORS:" + getMessage().getCorrectedBitCount() + -// " | " + getIschChannel0().toString() + " | " + getIschChannel1().toString(); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/isch/IISCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/isch/IISCH.java new file mode 100644 index 000000000..10ab8a16c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/isch/IISCH.java @@ -0,0 +1,132 @@ +/* + * ***************************************************************************** + * 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.phase2.message.isch; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase2.enumeration.ISCHSequence; +import io.github.dsheirer.module.decode.p25.phase2.enumeration.LCHType; +import io.github.dsheirer.module.decode.p25.phase2.enumeration.SuperframeSequence; +import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; +import java.util.Collections; +import java.util.List; + +/** + * Informational Inter-Slot Signaling Channel (I-ISCH) + */ +public class IISCH extends P25P2Message +{ + private static final IntField LCH_TYPE = IntField.range(0, 1); + private static final IntField CHANNEL_NUMBER = IntField.range(2, 3); + private static final IntField ISCH_SEQUENCE = IntField.range(4, 5); + private static final int LCH_FLAG = 6; + private static final IntField SUPERFRAME_SEQUENCE = IntField.range(7, 8); + + /** + * Constructs the message + * + * @param message + * @param offset + * @param timeslot + * @param timestamp of the final bit of the message + */ + public IISCH(CorrectedBinaryMessage message, int offset, int timeslot, long timestamp) + { + super(message, offset, timeslot, timestamp); + + if(message.getCorrectedBitCount() >= 8) + { + setValid(false); + } + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("TS").append(getTimeslot()); + sb.append(" I-ISCH"); + if(!isValid()) + { + sb.append(" [CRC-ERROR]"); + } + sb.append(" ").append(getLCHType()).append(" TIMESLOT"); + sb.append(" ").append(getLCHFlagMeaning()); + sb.append(" ").append(getSuperframeSequence()); + sb.append(" ").append(getIschSequence()); +// sb.append(" MSG:").append(getMessage().toHexString()); + + return sb.toString(); + } + + /** + * + * @return + */ + public String getLCHFlagMeaning() + { + return getLCHType().getLCHFlagLabel(getMessage().get(LCH_FLAG)); + } + + /** + * Indicates how the timeslot is configured (VCH, DATA, or LCCH). + */ + public LCHType getLCHType() + { + return LCHType.fromValue(getInt(LCH_TYPE)); + } + + /** + * Timeslot for this ISCH + * + * @return timeslot 1 or 2 + */ + public int getTimeslot() + { + return getInt(CHANNEL_NUMBER) + 1; + } + + /** + * Indicates this ISCH sequence's location within a super-frame + * + * @return location 1, 2, or 3(final) + */ + public ISCHSequence getIschSequence() + { + return ISCHSequence.fromValue(getInt(ISCH_SEQUENCE)); + } + + /** + * Superframe sequence/location within an ultraframe + * + * @return location, 1-4 + */ + public SuperframeSequence getSuperframeSequence() + { + return SuperframeSequence.fromValue(getInt(SUPERFRAME_SEQUENCE)); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/isch/ISCHDecoder.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/isch/ISCHDecoder.java new file mode 100644 index 000000000..a1015517e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/isch/ISCHDecoder.java @@ -0,0 +1,178 @@ +/* + * ***************************************************************************** + * 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.phase2.message.isch; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import java.util.Map; +import java.util.TreeMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Informational Inter-slot Signalling Chaannel (I-ISCH) decoder + */ +public class ISCHDecoder +{ + private final static Logger mLog = LoggerFactory.getLogger(ISCHDecoder.class); + private static final IntField CHANNEL_NUMBER = IntField.range(2, 3); + private static final IntField ISCH_LOCATION = IntField.range(4, 5); + + private static final Map CODEWORD_MAP_TS1 = new TreeMap<>(); + private static final Map CODEWORD_MAP_TS2 = new TreeMap<>(); + private static final long[] GENERATOR = {0x8816CE36D7l, 0x201DFD4F64l, 0x100F4B1758l, 0x0C00DED18El, 0x020807F7FFl, + 0x09048D9B72l, 0x009DA3A171l, 0x0058CBAA4El, 0x00343D8597l}; + private static final long CODE_WORD_OFFSET = 0x184229d461l; + + /** + * Constructs the ISCH-I parsing class + */ + public ISCHDecoder() + { + for(int x = 0; x < 512; x++) + { + BinaryMessage message = new BinaryMessage(9); + message.load(0, 9, x); + + int channelNumber = message.getInt(CHANNEL_NUMBER) + 1; + int ischLocation = message.getInt(ISCH_LOCATION); + + long codeword = getCodeWord(message); + codeword ^= CODE_WORD_OFFSET; + + if(ischLocation != 3) + { + if(channelNumber == 1) + { + CODEWORD_MAP_TS1.put(codeword, message); + } + else if(channelNumber == 2) + { + CODEWORD_MAP_TS2.put(codeword, message); + } + } + } + } + + /** + * Creates a codeword from the binary message where each set bit in the binary message corresponds to one of the + * words in the generator and we XOR the words together. + * @param message for generating a codeword + * @return codeword + */ + public static long getCodeWord(BinaryMessage message) + { + long codeword = 0; + + for(int x = 0; x < message.length(); x++) + { + if(message.get(x)) + { + codeword ^= GENERATOR[x]; + } + } + + return codeword; + } + + /** + * Decodes the 40-bit message codeword into an error-corrected 9-bit message. The expected timeslot allows us to + * only use the codewords that are correct for the expected timeslot. + * @param message containing 40 bit codeword + * @param expectedTimeslot either 1 or 2 + */ + public CorrectedBinaryMessage decode(BinaryMessage message, int expectedTimeslot) + { + long codeword = message.getLong(0, 39); + + if(expectedTimeslot == 1) + { + if(CODEWORD_MAP_TS1.containsKey(codeword)) + { + return new CorrectedBinaryMessage(CODEWORD_MAP_TS1.get(codeword)); + } + else + { + int smallestErrorCount = 8; + long closestCodeword = 0; + + for(long validCodeword: CODEWORD_MAP_TS1.keySet()) + { + long mask = codeword & validCodeword; + int errorCount = Long.bitCount(mask); + + if(errorCount < smallestErrorCount) + { + smallestErrorCount = errorCount; + closestCodeword = validCodeword; + } + } + + if(closestCodeword != 0 && smallestErrorCount <= 7) //Max correctable bit errors of 7 + { + CorrectedBinaryMessage decoded = new CorrectedBinaryMessage(CODEWORD_MAP_TS1.get(closestCodeword)); + decoded.setCorrectedBitCount(smallestErrorCount); + return decoded; + } + } + } + else if(expectedTimeslot == 2) + { + if(CODEWORD_MAP_TS2.containsKey(codeword)) + { + return new CorrectedBinaryMessage(CODEWORD_MAP_TS2.get(codeword)); + } + else + { + int smallestErrorCount = 16; + long closestCodeword = 0; + + for(long validCodeword: CODEWORD_MAP_TS2.keySet()) + { + long mask = codeword & validCodeword; + int errorCount = Long.bitCount(mask); + + if(errorCount < smallestErrorCount) + { + smallestErrorCount = errorCount; + closestCodeword = validCodeword; + } + } + + if(closestCodeword != 0 && smallestErrorCount <= 7) //Max correctable bit errors of 7 + { + CorrectedBinaryMessage decoded = new CorrectedBinaryMessage(CODEWORD_MAP_TS2.get(closestCodeword)); + decoded.setCorrectedBitCount(smallestErrorCount); + return decoded; + } + } + } + else + { + mLog.warn("Unexpected timeslot value: " + expectedTimeslot); + } + + //This shouldn't happen, but we'll set bit error count to 9 to indicate a bad decode + CorrectedBinaryMessage decoded = new CorrectedBinaryMessage(9); + decoded.setCorrectedBitCount(9); + return decoded; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/isch/SISCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/isch/SISCH.java new file mode 100644 index 000000000..4628db605 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/isch/SISCH.java @@ -0,0 +1,60 @@ +/* + * ***************************************************************************** + * 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.phase2.message.isch; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; +import java.util.Collections; +import java.util.List; + +/** + * Synchronizing Inter-Slot Signaling Channel (S-ISCH) + */ +public class SISCH extends P25P2Message +{ + /** + * Constructs the message + * + * @param message + * @param offset + * @param timeslot + * @param timestamp of the final bit of the message + */ + public SISCH(CorrectedBinaryMessage message, int offset, int timeslot, long timestamp) + { + super(message, offset, timeslot, timestamp); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("TS").append(getTimeslot()); + sb.append(" S-ISCH ").append(getMessage().toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/IP25ChannelGrantDetailProvider.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/IP25ChannelGrantDetailProvider.java new file mode 100644 index 000000000..90b510197 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/IP25ChannelGrantDetailProvider.java @@ -0,0 +1,50 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac; + +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; + +/** + * Channel grant details + */ +public interface IP25ChannelGrantDetailProvider +{ + /** + * Channel where the call will take place + */ + APCO25Channel getChannel(); + + /** + * Optional from radio unit. This can be null. + */ + Identifier getSourceAddress(); + + /** + * Target radio unit or talkgroup + */ + Identifier getTargetAddress(); + + /** + * Service options for the call. + */ + ServiceOptions getServiceOptions(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessage.java index ebdb9b69a..6dd8e2d81 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessage.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -20,11 +20,14 @@ package io.github.dsheirer.module.decode.p25.phase2.message.mac; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Nac; import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID; import io.github.dsheirer.module.decode.p25.phase2.enumeration.Voice4VOffset; import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; - +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure; +import java.util.ArrayList; import java.util.List; /** @@ -32,14 +35,12 @@ */ public class MacMessage extends P25P2Message { - private static int[] PDU_TYPE = {0, 1, 2}; - private static int[] OFFSET_TO_NEXT_VOICE_4V_START = {3, 4, 5}; - private static int[] RESERVED = {6, 7}; - - private int mChannelNumber; + private static final IntField PDU_TYPE = IntField.range(0, 2); + private static final IntField OFFSET_TO_NEXT_VOICE_4V_START = IntField.range(3, 5); + private static final IntField RESERVED = IntField.range(6, 7); private DataUnitID mDataUnitID; - private CorrectedBinaryMessage mMessage; private MacStructure mMacStructure; + private Identifier mNAC; /** * Constructs the message @@ -52,43 +53,52 @@ public class MacMessage extends P25P2Message public MacMessage(int timeslot, DataUnitID dataUnitID, CorrectedBinaryMessage message, long timestamp, MacStructure macStructure) { - super(timestamp); - mChannelNumber = timeslot; + super(message, 0, timeslot, timestamp); mDataUnitID = dataUnitID; - mMessage = message; mMacStructure = macStructure; } /** - * Number of bit errors that were corrected + * Assigns the NAC value that is optionally decoded from the LCCH mac structure. + * @param nac value to assign */ - public int getBitErrorCount() + public void setNAC(int nac) { - return getMessage().getCorrectedBitCount(); + mNAC = APCO25Nac.create(nac); } /** - * Timeslot / Channel number for this message + * NAC value when available. + * @return NAC value or zero if the value is unavailable (@code hasNAC()). */ - public int getTimeslot() + public Identifier getNAC() { - return mChannelNumber; + return mNAC; } /** - * Data Unit ID or timeslot type for this message + * Indicates if this mac message has a non-zero NAC value. + * @return true if available. */ - public DataUnitID getDataUnitID() + public boolean hasNAC() { - return mDataUnitID; + return mNAC != null; + } + + /** + * Number of bit errors that were corrected + */ + public int getBitErrorCount() + { + return getMessage().getCorrectedBitCount(); } /** - * Underlying binary message as transmitted and error-correctede + * Data Unit ID or timeslot type for this message */ - protected CorrectedBinaryMessage getMessage() + public DataUnitID getDataUnitID() { - return mMessage; + return mDataUnitID; } /** @@ -99,6 +109,15 @@ public MacStructure getMacStructure() return mMacStructure; } + /** + * Assigns a new mac structure to this mac message. + * @param macStructure to assign. + */ + public void setMacStructure(MacStructure macStructure) + { + mMacStructure = macStructure; + } + /** * MAC opcode identifies the type of MAC PDU for this message */ @@ -128,7 +147,14 @@ public Voice4VOffset getOffsetToNextVoice4VStart() @Override public List getIdentifiers() { - return getMacStructure().getIdentifiers(); + List identifiers = new ArrayList<>(); + identifiers.addAll(getMacStructure().getIdentifiers()); + if(hasNAC()) + { + identifiers.add(getNAC()); + } + + return identifiers; } @Override @@ -138,16 +164,19 @@ public String toString() sb.append("TS").append(getTimeslot()); sb.append(" ").append(getDataUnitID()); - if(isValid()) + if(!isValid()) { - sb.append(" ").append(getMacPduType().toString()); - sb.append(" ").append(getMacStructure().toString()); + sb.append(" [CRC ERROR]"); } - else + + if(hasNAC()) { - sb.append(" INVALID/CRC ERROR"); + sb.append(" NAC:").append(getNAC()); } + sb.append(" ").append(getMacPduType().toString()); + sb.append(" ").append(getMacStructure().toString()); + return sb.toString(); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java index 9f1a54177..cd01aa27b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -20,72 +20,129 @@ package io.github.dsheirer.module.decode.p25.phase2.message.mac; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.edac.CRCP25; import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AcknowledgeResponse; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AcknowledgeResponseFNEAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AcknowledgeResponseFNEExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastExtendedExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AdjacentStatusBroadcastImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AuthenticationDemand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AuthenticationFNEResponseAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AuthenticationFNEResponseExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.CallAlertAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.CallAlertExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.DateAndTimeAnnouncement; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.CallAlertExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.CallAlertExtendedVCH; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.DenyResponse; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.EndPushToTalk; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommand; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommandExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommandAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommandExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.ExtendedFunctionCommandExtendedVCH; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdate; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdateTDMA; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdateTDMAAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdateTDMAExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.FrequencyBandUpdateVUHF; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupAffiliationQueryAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupAffiliationQueryExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupPagingMessage; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupAffiliationResponseAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupAffiliationResponseExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupRegroupVoiceChannelUserAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateExplicit; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateMultiple; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateMultipleExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelGrantUpdateMultipleImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceServiceRequest; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.IndividualPagingMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.IndirectGroupPagingWithoutPriority; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.IndividualPagingWithPriority; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.LocationRegistrationResponse; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacRelease; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureFailedPDUCRC; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVariableLength; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MessageUpdateAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MessageUpdateExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NullInformationMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MessageUpdateExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MessageUpdateExtendedVCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MultiFragmentContinuationMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NetworkStatusBroadcastImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NullAvoidZeroBiasInformation; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.NullInformation; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PowerControlSignalQuality; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PushToTalk; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.QueuedResponse; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommand; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandEnhanced; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RfssStatusBroadcastAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RfssStatusBroadcastExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SNDCPDataChannelAnnouncementExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorCommandExtendedVCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorEnhancedCommandAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RadioUnitMonitorEnhancedCommandExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RfssStatusBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RfssStatusBroadcastImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RoamingAddressCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.RoamingAddressUpdate; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SNDCPDataChannelAnnouncement; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SNDCPDataChannelGrant; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SNDCPDataPageRequest; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SecondaryControlChannelBroadcastAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SecondaryControlChannelBroadcastExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SecondaryControlChannelBroadcastImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusQueryAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusQueryExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusQueryExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusQueryExtendedVCH; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusUpdateAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusUpdateExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusUpdateExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.StatusUpdateExtendedVCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SynchronizationBroadcast; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.SystemServiceBroadcast; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectAnswerRequest; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelGrantExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelGrantImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelGrantUpdateExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelGrantUpdateImplicit; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelUser; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TimeAndDateAnnouncement; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitDeRegistrationAcknowledge; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitRegistrationCommandAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitRegistrationResponseAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitRegistrationResponseExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitAnswerRequestAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitAnswerRequestExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateAbbreviated; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateExtendedVCH; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownStructure; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceServiceChannelGrantAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceServiceChannelGrantExtendedLCCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceServiceChannelGrantExtendedVCH; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownMacStructure; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownVendorMessage; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisRegroupCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisGroupRegroupExplicitEncryptionCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisPrivateDataChannelGrant; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisTalkerAlias; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisTalkerGpsLocation; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisUnitToUnitDataChannelGrant; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisUnknownOpcode129; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisUnknownOpcode143; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.UnknownOpcode136; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaAcknowledgeResponse; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaDenyResponse; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupAddCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantExplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantImplicit; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantUpdate; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupDeleteCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupExtendedFunctionCommand; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupOpcode145; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUpdate; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserAbbreviated; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaOpcode149; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaQueuedResponse; +import io.github.dsheirer.module.decode.p25.reference.Vendor; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; @@ -100,45 +157,88 @@ public class MacMessageFactory private final static Logger mLog = LoggerFactory.getLogger(MacMessageFactory.class); - public static List create(int timeslot, DataUnitID dataUnitID, - CorrectedBinaryMessage message, long timestamp) + /** + * Creates a set of MAC messages + * @param timeslot for the messages + * @param dataUnitID indicates the type of message + * @param message to process + * @param timestamp for the message + * @param maxIndex is the maximum bit index that contains octet data in this message + * @return list of one or more mac messages + */ + public static List create(int timeslot, DataUnitID dataUnitID, CorrectedBinaryMessage message, + long timestamp, int maxIndex) { List messages = new ArrayList<>(); + boolean passesCRC = true; + + int nac = 0; + + if(dataUnitID.isFACCH()) + { + passesCRC = CRCP25.crc12_FACCH(message); + } + else if(dataUnitID.isLCCH()) + { + passesCRC = CRCP25.crc16_LCCH(message); + + if(passesCRC) + { + nac = MacStructure.getLcchNac(message); + } + } + else if(dataUnitID.isSACCH()) + { + passesCRC = CRCP25.crc12_SACCH(message); + } + + if(!passesCRC) + { + MacMessage mac = new MacMessage(timeslot, dataUnitID, message, timestamp, new MacStructureFailedPDUCRC(message, 0)); + mac.setValid(false); + messages.add(mac); + return messages; + } + MacPduType macPduType = MacMessage.getMacPduTypeFromMessage(message); switch(macPduType) { - case MAC_0_RESERVED: - break; case MAC_1_PTT: messages.add(new MacMessage(timeslot, dataUnitID, message, timestamp, new PushToTalk(message))); break; case MAC_2_END_PTT: messages.add(new MacMessage(timeslot, dataUnitID, message, timestamp, new EndPushToTalk(message))); break; + case MAC_0_SIGNAL: case MAC_3_IDLE: case MAC_4_ACTIVE: case MAC_6_HANGTIME: - List indices = getMacStructureIndices(message); + List indices = getMacStructureIndices(message, maxIndex); for(Integer index : indices) { MacStructure macStructure = createMacStructure(message, index); - messages.add(new MacMessage(timeslot, dataUnitID, message, timestamp, macStructure)); + MacMessage macMessage = new MacMessage(timeslot, dataUnitID, message, timestamp, macStructure); + messages.add(macMessage); } break; - case MAC_5_RESERVED: - break; - case MAC_7_RESERVED: - break; - case MAC_UNKNOWN: - break; default: messages.add(new UnknownMacMessage(timeslot, dataUnitID, message, timestamp)); break; } + //Assign the nac value parsed from the LCCH MAC PDU content paylaod when it is non-zero + if(nac > 0) + { + for(MacMessage macMessage: messages) + { + macMessage.setNAC(nac); + } + } + + return messages; } @@ -146,9 +246,10 @@ public static List create(int timeslot, DataUnitID dataUnitID, * Identifies the MAC structure start indices for the message * * @param message containing one or more MAC structures + * @param maxIndex is the maximum bit index that is parseable in this structure * @return structure start indices */ - private static List getMacStructureIndices(CorrectedBinaryMessage message) + private static List getMacStructureIndices(CorrectedBinaryMessage message, int maxIndex) { List indices = new ArrayList<>(); @@ -156,30 +257,34 @@ private static List getMacStructureIndices(CorrectedBinaryMessage messa indices.add(DEFAULT_MAC_STRUCTURE_INDEX); MacOpcode opcode = MacStructure.getOpcode(message, DEFAULT_MAC_STRUCTURE_INDEX); + opcode = checkVendorOpcode(opcode, message, DEFAULT_MAC_STRUCTURE_INDEX); - int opcodeLength = opcode.getLength(); + int opcodeLength = getLength(opcode, message, DEFAULT_MAC_STRUCTURE_INDEX, maxIndex); - if(opcodeLength > 0 && opcode != MacOpcode.TDMA_0_NULL_INFORMATION_MESSAGE) + if(opcodeLength > 0 && opcode != MacOpcode.TDMA_00_NULL_INFORMATION_MESSAGE) { - int secondStructureIndex = DEFAULT_MAC_STRUCTURE_INDEX + (opcode.getLength() * 8); + int secondStructureIndex = DEFAULT_MAC_STRUCTURE_INDEX + (opcodeLength * 8); - if(secondStructureIndex < message.size()) + if(secondStructureIndex < maxIndex) { MacOpcode secondOpcode = MacStructure.getOpcode(message, secondStructureIndex); + secondOpcode = checkVendorOpcode(secondOpcode, message, secondStructureIndex); - if(secondOpcode != MacOpcode.TDMA_0_NULL_INFORMATION_MESSAGE) + if(secondOpcode != MacOpcode.TDMA_00_NULL_INFORMATION_MESSAGE) { indices.add(secondStructureIndex); - if(secondOpcode.getLength() > 0) + opcodeLength = getLength(secondOpcode, message, secondStructureIndex, maxIndex); + + if(opcodeLength > 0) { - int thirdStructureIndex = secondStructureIndex + (secondOpcode.getLength() * 8); + int thirdStructureIndex = secondStructureIndex + (opcodeLength * 8); - if(thirdStructureIndex < message.size()) + if(thirdStructureIndex < maxIndex) { MacOpcode thirdOpcode = MacStructure.getOpcode(message, thirdStructureIndex); - if(thirdOpcode != MacOpcode.TDMA_0_NULL_INFORMATION_MESSAGE) + if(thirdOpcode != MacOpcode.TDMA_00_NULL_INFORMATION_MESSAGE) { indices.add(thirdStructureIndex); } @@ -192,6 +297,59 @@ private static List getMacStructureIndices(CorrectedBinaryMessage messa return indices; } + /** + * Calculates the structure length for the opcode. + * @param opcode for the structure + * @param message containing bits + * @param offset to the start of the structure being inspected + * @param maxIndex for this PDU content field. + * @return length of the structure in octets. + */ + private static int getLength(MacOpcode opcode, CorrectedBinaryMessage message, int offset, int maxIndex) + { + //How many octets are available in the message - total length minus offset minus 12 bits of CRC + int availableOctets = Math.ceilDiv(maxIndex - offset, 8); + + if(opcode.isVariableLength()) + { + if(opcode.isVendorPartition()) + { + return MacStructureVendor.getLength(message, offset); + } + else + { + return MacStructureVariableLength.getLength(message, offset); + } + } + else if(opcode.isUnknownLength()) + { + return availableOctets; + } + else + { + return opcode.getLength(); + } + } + + /** + * Checks to see if the opcode is a vendor opcode and returns the correct vendor version of the opcode. + * @param opcode to check + * @param message that possibly contains a vendor identifier + * @param index to the start of the mac structure in this message + * @return original opcode or the vendor opcode when appropriate. + */ + private static MacOpcode checkVendorOpcode(MacOpcode opcode, CorrectedBinaryMessage message, int index) + { + if(opcode == MacOpcode.VENDOR_PARTITION_2_UNKNOWN_OPCODE) + { + Vendor vendor = MacStructureVendor.getVendor(message, index); + int opcodeNumber = MacStructure.getOpcodeNumber(message, index); + return MacOpcode.fromValue(opcodeNumber, vendor); + } + + return opcode; + } + /** * Creates a MAC structure parser for the message with the specified structure start offset. * @@ -202,138 +360,277 @@ private static List getMacStructureIndices(CorrectedBinaryMessage messa public static MacStructure createMacStructure(CorrectedBinaryMessage message, int offset) { MacOpcode opcode = MacStructure.getOpcode(message, offset); + return createMacStructure(message, offset, opcode); + } + /** + * Creates a MAC structure parser for the message with the specified structure start offset and opcode + * + * @param message containing a MAC structure + * @param offset to the start of the structure + * @return MAC structure parser + */ + public static MacStructure createMacStructure(CorrectedBinaryMessage message, int offset, MacOpcode opcode) + { switch(opcode) { - case TDMA_0_NULL_INFORMATION_MESSAGE: - return new NullInformationMessage(message, offset); - case TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED: + case TDMA_00_NULL_INFORMATION_MESSAGE: + return new NullInformation(message, offset); + case TDMA_01_GROUP_VOICE_CHANNEL_USER_ABBREVIATED: return new GroupVoiceChannelUserAbbreviated(message, offset); - case TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER: + case TDMA_02_UNIT_TO_UNIT_VOICE_CHANNEL_USER_ABBREVIATED: return new UnitToUnitVoiceChannelUserAbbreviated(message, offset); - case TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: + case TDMA_03_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: return new TelephoneInterconnectVoiceChannelUser(message, offset); - case TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE: - return new GroupVoiceChannelGrantUpdateMultiple(message, offset); - case TDMA_17_INDIRECT_GROUP_PAGING: - return new GroupPagingMessage(message, offset); - case TDMA_18_INDIVIDUAL_PAGING_MESSAGE_WITH_PRIORITY: - return new IndividualPagingMessage(message, offset); - case TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED: + case TDMA_05_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_IMPLICIT: + return new GroupVoiceChannelGrantUpdateMultipleImplicit(message, offset); + case TDMA_08_NULL_AVOID_ZERO_BIAS: + return new NullAvoidZeroBiasInformation(message, offset); + case TDMA_10_MULTI_FRAGMENT_CONTINUATION_MESSAGE: + return new MultiFragmentContinuationMessage(message, offset); + case TDMA_11_INDIRECT_GROUP_PAGING_WITHOUT_PRIORITY: + return new IndirectGroupPagingWithoutPriority(message, offset); + case TDMA_12_INDIVIDUAL_PAGING_WITH_PRIORITY: + return new IndividualPagingWithPriority(message, offset); + case TDMA_21_GROUP_VOICE_CHANNEL_USER_EXTENDED: return new GroupVoiceChannelUserExtended(message, offset); - case TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: + case TDMA_22_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: return new UnitToUnitVoiceChannelUserExtended(message, offset); - case TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT: + case TDMA_25_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT: return new GroupVoiceChannelGrantUpdateMultipleExplicit(message, offset); - case TDMA_48_POWER_CONTROL_SIGNAL_QUALITY: + case TDMA_30_POWER_CONTROL_SIGNAL_QUALITY: return new PowerControlSignalQuality(message, offset); - case TDMA_49_MAC_RELEASE: + case TDMA_31_MAC_RELEASE: return new MacRelease(message, offset); - case PHASE1_64_GROUP_VOICE_CHANNEL_GRANT_ABBREVIATED: - return new GroupVoiceChannelGrantAbbreviated(message, offset); - case PHASE1_65_GROUP_VOICE_SERVICE_REQUEST: + case PHASE1_40_GROUP_VOICE_CHANNEL_GRANT_IMPLICIT: + return new GroupVoiceChannelGrantImplicit(message, offset); + case PHASE1_41_GROUP_VOICE_SERVICE_REQUEST: return new GroupVoiceServiceRequest(message, offset); - case PHASE1_66_GROUP_VOICE_CHANNEL_GRANT_UPDATE: - return new GroupVoiceChannelGrantUpdate(message, offset); - case PHASE1_68_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_ABBREVIATED: - return new UnitToUnitVoiceChannelGrantAbbreviated(message, offset); - case PHASE1_69_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED: + case PHASE1_42_GROUP_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: + return new GroupVoiceChannelGrantUpdateImplicit(message, offset); + case PHASE1_44_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_ABBREVIATED: + return new UnitToUnitVoiceServiceChannelGrantAbbreviated(message, offset); + case PHASE1_45_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED: return new UnitToUnitAnswerRequestAbbreviated(message, offset); - case PHASE1_70_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED: + case PHASE1_46_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED: return new UnitToUnitVoiceChannelGrantUpdateAbbreviated(message, offset); - case PHASE1_74_TELEPHONE_INTERCONNECT_ANSWER_REQUEST: + case PHASE1_48_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_IMPLICIT: + return new TelephoneInterconnectVoiceChannelGrantImplicit(message, offset); + case PHASE1_49_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: + return new TelephoneInterconnectVoiceChannelGrantUpdateImplicit(message, offset); + case PHASE1_4A_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE: return new TelephoneInterconnectAnswerRequest(message, offset); - case PHASE1_76_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED: - return new RadioUnitMonitorCommand(message, offset); - case PHASE1_84_SNDCP_DATA_CHANNEL_GRANT: + case PHASE1_4C_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED: + return new RadioUnitMonitorCommandAbbreviated(message, offset); + case PHASE1_54_SNDCP_DATA_CHANNEL_GRANT: return new SNDCPDataChannelGrant(message, offset); - case PHASE1_85_SNDCP_DATA_PAGE_REQUEST: + case PHASE1_55_SNDCP_DATA_PAGE_REQUEST: return new SNDCPDataPageRequest(message, offset); - case PHASE1_88_STATUS_UPDATE_ABBREVIATED: + case PHASE1_58_STATUS_UPDATE_ABBREVIATED: return new StatusUpdateAbbreviated(message, offset); - case PHASE1_90_STATUS_QUERY_ABBREVIATED: + case PHASE1_5A_STATUS_QUERY_ABBREVIATED: return new StatusQueryAbbreviated(message, offset); - case OBSOLETE_PHASE1_93_RADIO_UNIT_MONITOR_COMMAND: - return new UnknownStructure(message, offset); //Message is obsolete -- return unknown - case PHASE1_92_MESSAGE_UPDATE_ABBREVIATED: + case PHASE1_5D_RADIO_UNIT_MONITOR_COMMAND_OBSOLETE: + return new UnknownMacStructure(message, offset); //Message is obsolete -- return unknown + case PHASE1_5C_MESSAGE_UPDATE_ABBREVIATED: return new MessageUpdateAbbreviated(message, offset); - case PHASE1_94_RADIO_UNIT_MONITOR_COMMAND_ENHANCED: - return new RadioUnitMonitorCommandEnhanced(message, offset); - case PHASE1_95_CALL_ALERT_ABBREVIATED: + case PHASE1_5E_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_ABBREVIATED: + return new RadioUnitMonitorEnhancedCommandAbbreviated(message, offset); + case PHASE1_5F_CALL_ALERT_ABBREVIATED: return new CallAlertAbbreviated(message, offset); - case PHASE1_96_ACK_RESPONSE: - return new AcknowledgeResponse(message, offset); - case PHASE1_97_QUEUED_RESPONSE: + case PHASE1_60_ACKNOWLEDGE_RESPONSE_FNE_ABBREVIATED: + return new AcknowledgeResponseFNEAbbreviated(message, offset); + case PHASE1_61_QUEUED_RESPONSE: return new QueuedResponse(message, offset); - case PHASE1_100_EXTENDED_FUNCTION_COMMAND_ABBREVIATED: - return new ExtendedFunctionCommand(message, offset); - case PHASE1_103_DENY_RESPONSE: + case PHASE1_64_EXTENDED_FUNCTION_COMMAND_ABBREVIATED: + return new ExtendedFunctionCommandAbbreviated(message, offset); + case PHASE1_67_DENY_RESPONSE: return new DenyResponse(message, offset); - case PHASE1_106_GROUP_AFFILIATION_QUERY_ABBREVIATED: + case PHASE1_68_GROUP_AFFILIATION_RESPONSE_ABBREVIATED: + return new GroupAffiliationResponseAbbreviated(message, offset); + case PHASE1_6A_GROUP_AFFILIATION_QUERY_ABBREVIATED: return new GroupAffiliationQueryAbbreviated(message, offset); - case PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED: + case PHASE1_6B_LOCATION_REGISTRATION_RESPONSE: + return new LocationRegistrationResponse(message, offset); + case PHASE1_6C_UNIT_REGISTRATION_RESPONSE_ABBREVIATED: + return new UnitRegistrationResponseAbbreviated(message, offset); + case PHASE1_6D_UNIT_REGISTRATION_COMMAND_ABBREVIATED: return new UnitRegistrationCommandAbbreviated(message, offset); - case PHASE1_115_IDENTIFIER_UPDATE_TDMA: - return new FrequencyBandUpdateTDMA(message, offset); - case PHASE1_116_IDENTIFIER_UPDATE_V_UHF: + case PHASE1_6F_DEREGISTRATION_ACKNOWLEDGE: + return new UnitDeRegistrationAcknowledge(message, offset); + case PHASE1_70_SYNCHRONIZATION_BROADCAST: + return new SynchronizationBroadcast(message, offset); + case PHASE1_71_AUTHENTICATION_DEMAND: + return new AuthenticationDemand(message, offset); + case PHASE1_72_AUTHENTICATION_FNE_RESPONSE_ABBREVIATED: + return new AuthenticationFNEResponseAbbreviated(message, offset); + case PHASE1_73_IDENTIFIER_UPDATE_TDMA_ABBREVIATED: + return new FrequencyBandUpdateTDMAAbbreviated(message, offset); + case PHASE1_74_IDENTIFIER_UPDATE_V_UHF: return new FrequencyBandUpdateVUHF(message, offset); - case PHASE1_117_TIME_AND_DATE_ANNOUNCEMENT: - return new DateAndTimeAnnouncement(message, offset); - case PHASE1_120_SYSTEM_SERVICE_BROADCAST: + case PHASE1_75_TIME_AND_DATE_ANNOUNCEMENT: + return new TimeAndDateAnnouncement(message, offset); + case PHASE1_76_ROAMING_ADDRESS_COMMAND: + return new RoamingAddressCommand(message, offset); + case PHASE1_77_ROAMING_ADDRESS_UPDATE: + return new RoamingAddressUpdate(message, offset); + case PHASE1_78_SYSTEM_SERVICE_BROADCAST: return new SystemServiceBroadcast(message, offset); - case PHASE1_121_SECONDARY_CONTROL_CHANNEL_BROADCAST_ABBREVIATED: - return new SecondaryControlChannelBroadcastAbbreviated(message, offset); - case PHASE1_122_RFSS_STATUS_BROADCAST_ABBREVIATED: - return new RfssStatusBroadcastAbbreviated(message, offset); - case PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED: - return new NetworkStatusBroadcastAbbreviated(message, offset); - case PHASE1_124_ADJACENT_STATUS_BROADCAST_ABBREVIATED: - return new AdjacentStatusBroadcastAbbreviated(message, offset); - case PHASE1_125_IDENTIFIER_UPDATE: + case PHASE1_79_SECONDARY_CONTROL_CHANNEL_BROADCAST_IMPLICIT: + return new SecondaryControlChannelBroadcastImplicit(message, offset); + case PHASE1_7A_RFSS_STATUS_BROADCAST_IMPLICIT: + return new RfssStatusBroadcastImplicit(message, offset); + case PHASE1_7B_NETWORK_STATUS_BROADCAST_IMPLICIT: + return new NetworkStatusBroadcastImplicit(message, offset); + case PHASE1_7C_ADJACENT_STATUS_BROADCAST_IMPLICIT: + return new AdjacentStatusBroadcastImplicit(message, offset); + case PHASE1_7D_IDENTIFIER_UPDATE: return new FrequencyBandUpdate(message, offset); - case PHASE1_168_L3HARRIS_TALKER_ALIAS: - return new L3HarrisTalkerAlias(message, offset); - case PHASE1_176_L3HARRIS_GROUP_REGROUP: - return new L3HarrisRegroupCommand(message, offset); - case PHASE1_192_GROUP_VOICE_CHANNEL_GRANT_EXTENDED: - return new GroupVoiceChannelGrantExtended(message, offset); - case PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + case PHASE1_88_UNKNOWN_LCCH_OPCODE: + return new UnknownOpcode136(message, offset); + case PHASE1_90_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED: + return new GroupRegroupVoiceChannelUserAbbreviated(message, offset); + case PHASE1_C0_GROUP_VOICE_CHANNEL_GRANT_EXPLICIT: + return new GroupVoiceChannelGrantExplicit(message, offset); + case PHASE1_C3_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: return new GroupVoiceChannelGrantUpdateExplicit(message, offset); - case PHASE1_196_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_EXTENDED: - return new UnitToUnitVoiceChannelGrantExtended(message, offset); - case PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED: + case PHASE1_C4_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_VCH: + return new UnitToUnitVoiceServiceChannelGrantExtendedVCH(message, offset); + case PHASE1_C5_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED: return new UnitToUnitAnswerRequestExtended(message, offset); - case PHASE1_198_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED: - return new UnitToUnitVoiceChannelGrantUpdateExtended(message, offset); - case PHASE1_204_RADIO_UNIT_MONITOR_COMMAND_EXTENDED: - return new RadioUnitMonitorCommandExtended(message, offset); - case PHASE1_214_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: - return new SNDCPDataChannelAnnouncementExplicit(message, offset); - case PHASE1_216_STATUS_UPDATE_EXTENDED: - return new StatusUpdateExtended(message, offset); - case PHASE1_218_STATUS_QUERY_EXTENDED: - return new StatusQueryExtended(message, offset); - case PHASE1_220_MESSAGE_UPDATE_EXTENDED: - return new MessageUpdateExtended(message, offset); - case PHASE1_223_CALL_ALERT_EXTENDED: - return new CallAlertExtended(message, offset); - case PHASE1_228_EXTENDED_FUNCTION_COMMAND_EXTENDED: - return new ExtendedFunctionCommandExtended(message, offset); - case PHASE1_233_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + case PHASE1_C6_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_VCH: + return new UnitToUnitVoiceChannelGrantUpdateExtendedVCH(message, offset); + case PHASE1_C7_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_LCCH: + return new UnitToUnitVoiceChannelGrantUpdateExtendedLCCH(message, offset); + case PHASE1_C8_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_EXPLICIT: + return new TelephoneInterconnectVoiceChannelGrantExplicit(message, offset); + case PHASE1_C9_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + return new TelephoneInterconnectVoiceChannelGrantUpdateExplicit(message, offset); + case PHASE1_CB_CALL_ALERT_EXTENDED_LCCH: + return new CallAlertExtendedLCCH(message, offset); + case PHASE1_CC_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_VCH: + return new RadioUnitMonitorCommandExtendedVCH(message, offset); + case PHASE1_CD_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_LCCH: + return new RadioUnitMonitorCommandExtendedLCCH(message, offset); + case PHASE1_CE_MESSAGE_UPDATE_EXTENDED_LCCH: + return new MessageUpdateExtendedLCCH(message, offset); + case PHASE1_CF_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_LCCH: + return new UnitToUnitVoiceServiceChannelGrantExtendedLCCH(message, offset); + case PHASE1_D6_SNDCP_DATA_CHANNEL_ANNOUNCEMENT: + return new SNDCPDataChannelAnnouncement(message, offset); + case PHASE1_D8_STATUS_UPDATE_EXTENDED_VCH: + return new StatusUpdateExtendedVCH(message, offset); + case PHASE1_D9_STATUS_UPDATE_EXTENDED_LCCH: + return new StatusUpdateExtendedLCCH(message, offset); + case PHASE1_DA_STATUS_QUERY_EXTENDED_VCH: + return new StatusQueryExtendedVCH(message, offset); + case PHASE1_DB_STATUS_QUERY_EXTENDED_LCCH: + return new StatusQueryExtendedLCCH(message, offset); + case PHASE1_DC_MESSAGE_UPDATE_EXTENDED_VCH: + return new MessageUpdateExtendedVCH(message, offset); + case PHASE1_DE_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_EXTENDED: + return new RadioUnitMonitorEnhancedCommandExtended(message, offset); + case PHASE1_DF_CALL_ALERT_EXTENDED_VCH: + return new CallAlertExtendedVCH(message, offset); + case PHASE1_E0_ACKNOWLEDGE_RESPONSE_FNE_EXTENDED: + return new AcknowledgeResponseFNEExtended(message, offset); + case PHASE1_E4_EXTENDED_FUNCTION_COMMAND_EXTENDED_VCH: + return new ExtendedFunctionCommandExtendedVCH(message, offset); + case PHASE1_E5_EXTENDED_FUNCTION_COMMAND_EXTENDED_LCCH: + return new ExtendedFunctionCommandExtendedLCCH(message, offset); + case PHASE1_E8_GROUP_AFFILIATION_RESPONSE_EXTENDED: + return new GroupAffiliationResponseExtended(message, offset); + case PHASE1_E9_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: return new SecondaryControlChannelBroadcastExplicit(message, offset); - case PHASE1_234_GROUP_AFFILIATION_QUERY_EXTENDED: + case PHASE1_EA_GROUP_AFFILIATION_QUERY_EXTENDED: return new GroupAffiliationQueryExtended(message, offset); - case PHASE1_250_RFSS_STATUS_BROADCAST_EXTENDED: - return new RfssStatusBroadcastExtended(message, offset); - case PHASE1_251_NETWORK_STATUS_BROADCAST_EXTENDED: - return new NetworkStatusBroadcastExtended(message, offset); - case PHASE1_252_ADJACENT_STATUS_BROADCAST_EXTENDED: - return new AdjacentStatusBroadcastExtended(message, offset); + case PHASE1_EC_UNIT_REGISTRATION_RESPONSE_EXTENDED: + return new UnitRegistrationResponseExtended(message, offset); + case PHASE1_F2_AUTHENTICATION_FNE_RESPONSE_EXTENDED: + return new AuthenticationFNEResponseExtended(message, offset); + case PHASE1_F3_IDENTIFIER_UPDATE_TDMA_EXTENDED: + return new FrequencyBandUpdateTDMAExtended(message, offset); + case PHASE1_FA_RFSS_STATUS_BROADCAST_EXPLICIT: + return new RfssStatusBroadcastExplicit(message, offset); + case PHASE1_FB_NETWORK_STATUS_BROADCAST_EXPLICIT: + return new NetworkStatusBroadcastExplicit(message, offset); + case PHASE1_FC_ADJACENT_STATUS_BROADCAST_EXPLICIT: + return new AdjacentStatusBroadcastExplicit(message, offset); + case PHASE1_FE_ADJACENT_STATUS_BROADCAST_EXTENDED_EXPLICIT: + return new AdjacentStatusBroadcastExtendedExplicit(message, offset); + + case L3HARRIS_81_UNKNOWN_OPCODE_129: + return new L3HarrisUnknownOpcode129(message, offset); + case L3HARRIS_8F_UNKNOWN_OPCODE_143: + return new L3HarrisUnknownOpcode143(message, offset); + case L3HARRIS_A0_PRIVATE_DATA_CHANNEL_GRANT: + return new L3HarrisPrivateDataChannelGrant(message, offset); + case L3HARRIS_AA_GPS_LOCATION: + L3HarrisTalkerGpsLocation gps = new L3HarrisTalkerGpsLocation(message, offset); + mLog.info(gps.toString()); + return new L3HarrisTalkerGpsLocation(message, offset); + case L3HARRIS_A8_TALKER_ALIAS: + return new L3HarrisTalkerAlias(message, offset); + case L3HARRIS_AC_UNIT_TO_UNIT_DATA_CHANNEL_GRANT: + return new L3HarrisUnitToUnitDataChannelGrant(message, offset); + case L3HARRIS_B0_GROUP_REGROUP_EXPLICIT_ENCRYPTION_COMMAND: + return new L3HarrisGroupRegroupExplicitEncryptionCommand(message, offset); + + case MOTOROLA_80_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED: + return new MotorolaGroupRegroupVoiceChannelUserAbbreviated(message, offset); + case MOTOROLA_81_GROUP_REGROUP_ADD: + return new MotorolaGroupRegroupAddCommand(message, offset); + case MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE: + return new MotorolaGroupRegroupVoiceChannelUpdate(message, offset); + case MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND: + return new MotorolaGroupRegroupExtendedFunctionCommand(message, offset); + case MOTOROLA_89_GROUP_REGROUP_DELETE: + return new MotorolaGroupRegroupDeleteCommand(message, offset); + case MOTOROLA_91_GROUP_REGROUP_UNKNOWN: + return new MotorolaGroupRegroupOpcode145(message, offset); + case MOTOROLA_95_UNKNOWN_149: + return new MotorolaOpcode149(message, offset); + case MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED: + return new MotorolaGroupRegroupVoiceChannelUserExtended(message, offset); + case MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT: + return new MotorolaGroupRegroupChannelGrantImplicit(message, offset); + case MOTOROLA_A4_GROUP_REGROUP_CHANNEL_GRANT_EXPLICIT: + return new MotorolaGroupRegroupChannelGrantExplicit(message, offset); + case MOTOROLA_A5_GROUP_REGROUP_CHANNEL_GRANT_UPDATE: + return new MotorolaGroupRegroupChannelGrantUpdate(message, offset); + case MOTOROLA_A6_QUEUED_RESPONSE: + return new MotorolaQueuedResponse(message, offset); + case MOTOROLA_A7_DENY_RESPONSE: + return new MotorolaDenyResponse(message, offset); + case MOTOROLA_A8_ACKNOWLEDGE_RESPONSE: + return new MotorolaAcknowledgeResponse(message, offset); case VENDOR_PARTITION_2_UNKNOWN_OPCODE: + Vendor vendor = MacStructureVendor.getVendor(message, offset); + int opcodeNumber = MacStructure.getOpcodeNumber(message, offset); + + //L3Harris GPS seems to have an extra octet (0x80) where the opcode should be and the true opcode 0xAA is + //possibly one byte to the right... adjust for this. + if(vendor == Vendor.V170 && opcodeNumber == 0x80) + { + Vendor candidateVendor = MacStructureVendor.getVendor(message, offset + 8); + + if(candidateVendor == Vendor.HARRIS) + { + return createMacStructure(message, offset + 8, MacOpcode.L3HARRIS_AA_GPS_LOCATION); + } + } + + MacOpcode vendorOpcode = MacOpcode.fromValue(opcodeNumber, vendor); + + if(vendorOpcode != MacOpcode.VENDOR_PARTITION_2_UNKNOWN_OPCODE) + { + return createMacStructure(message, offset, vendorOpcode); + } + return new UnknownVendorMessage(message, offset); } - return new UnknownStructure(message, offset); + return new UnknownMacStructure(message, offset); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java index 59d6293cf..b05deb40c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -19,92 +19,161 @@ package io.github.dsheirer.module.decode.p25.phase2.message.mac; +import io.github.dsheirer.module.decode.p25.reference.Vendor; import java.util.EnumSet; import java.util.Map; import java.util.TreeMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * MAC opcode is used with MAC_IDLE, MAC_ACTIVE and MAC_HANGTIME PDU format messages + * + * Opcode Lengths used in the enumeration entries: + * a) >0 = discrete length + * b) Integer.MIN_VALUE = variable length + * c) -1 = unknown length */ public enum MacOpcode { - PUSH_TO_TALK(-1, "PUSH-TO-TALK", -1), - END_PUSH_TO_TALK(-1, "END_PUSH-TO-TALK", -1), - - TDMA_0_NULL_INFORMATION_MESSAGE(0, "NULL INFORMATION", -1), - TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED(1, "GROUP VOICE CHANNEL USER ABBREVIATED", 7), - TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER(2, "UNIT-TO-UNIT VOICE CHANNEL USER", 8), - TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER(3, "TELEPHONE INTERCONNECT VOICE CHANNEL USER", 7), - TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE(5, "GROUP VOICE CHANNEL GRANT UPDATE MULTIPLE", 16), - TDMA_17_INDIRECT_GROUP_PAGING(17, "INDIRECT GROUP PAGING", Integer.MIN_VALUE), - TDMA_18_INDIVIDUAL_PAGING_MESSAGE_WITH_PRIORITY(18, "INDIVIDUAL PAGING MESSAGE WITH PRIORITY", Integer.MIN_VALUE), - TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED(33, "GROUP VOICE CHANNEL USER EXTENDED", 14), - TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED(34, "UNIT-TO-UNIT VOICE CHANNEL USER EXTENDED", 15), - TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT(37, "TDMA GROUP VOICE CHANNEL GRANT UPDATE EXPLICIT", 15), - TDMA_48_POWER_CONTROL_SIGNAL_QUALITY(48, "POWER CONTROL SIGNAL QUALITY", 5), - TDMA_49_MAC_RELEASE(49, "MAC RELEASE", 7), - TDMA_PARTITION_0_UNKNOWN_OPCODE(-1, "UNKNOWN TDMA OPCODE", -1), - - PHASE1_64_GROUP_VOICE_CHANNEL_GRANT_ABBREVIATED(64, "GROUP VOICE CHANNEL GRANT ABBREVIATED", 9), - PHASE1_65_GROUP_VOICE_SERVICE_REQUEST(65, "GROUP VOICE SERVICE REQUEST", 7), - PHASE1_66_GROUP_VOICE_CHANNEL_GRANT_UPDATE(66, "GROUP VOICE CHANNEL GRANT UPDATE", 9), - PHASE1_68_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_ABBREVIATED(68, "UNIT-TO-UNIT VOICE CHANNEL GRANT ABBREVIATED", 9), - PHASE1_69_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED(69, "UNIT-TO-UNIT ANSWER REQUEST ABBREVIATED", 8), - PHASE1_70_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED(70, "UNIT-TO-UNIT VOICE CHANNEL GRANT UPDATE ABBREVIATED", 9), - PHASE1_74_TELEPHONE_INTERCONNECT_ANSWER_REQUEST(74, "TELEPHONE INTERCONNECT_ANSWER REQUEST", 9), - PHASE1_76_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED(76, "RADIO UNIT MONITOR COMMAND ABBREVIATED", 10), - PHASE1_84_SNDCP_DATA_CHANNEL_GRANT(84, "SNDCP DATA CHANNEL GRANT", 9), - PHASE1_85_SNDCP_DATA_PAGE_REQUEST(85, "SNDCP DATA PAGE REQUEST", 7), - PHASE1_88_STATUS_UPDATE_ABBREVIATED(88, "STATUS UPDATE ABBREVIATED", 10), - PHASE1_90_STATUS_QUERY_ABBREVIATED(90, "STATUS QUERY ABBREVIATED", 7), - PHASE1_92_MESSAGE_UPDATE_ABBREVIATED(92, "MESSAGE UPDATE ABBREVIATED", 10), - OBSOLETE_PHASE1_93_RADIO_UNIT_MONITOR_COMMAND(93, "RADIO UNIT MONITOR COMMAND", 8), - PHASE1_94_RADIO_UNIT_MONITOR_COMMAND_ENHANCED(94, "RADIO UNIT MONITOR ENHANCED COMMAND ABBREVIATED", 14), - PHASE1_95_CALL_ALERT_ABBREVIATED(95, "CALL ALERT ABBREVIATED", 7), - PHASE1_96_ACK_RESPONSE(96, "ACK RESPONSE", 9), - PHASE1_97_QUEUED_RESPONSE(97, "QUEUED RESPONSE", 9), - PHASE1_100_EXTENDED_FUNCTION_COMMAND_ABBREVIATED(100, "EXTENDED FUNCTION COMMAND ABBREVIATED", 9), - PHASE1_103_DENY_RESPONSE(103, "DENY RESPONSE", 9), - PHASE1_106_GROUP_AFFILIATION_QUERY_ABBREVIATED(106, "GROUP AFFILIATION QUERY ABBREVIATED", 7), - PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED(109, "UNIT REGISTRATION COMMAND ABBREVIATED", 7), - PHASE1_115_IDENTIFIER_UPDATE_TDMA(115, "IDENTIFIER UPDATE TDMA", 9), - PHASE1_116_IDENTIFIER_UPDATE_V_UHF(116, "IDENTIFIER UPDATE V/UHF", 9), - PHASE1_117_TIME_AND_DATE_ANNOUNCEMENT(117, "TIME AND DATE ANNOUNCEMENT", 9), - PHASE1_120_SYSTEM_SERVICE_BROADCAST(120, "SYSTEM SERVICE BROADCAST", 9), - PHASE1_121_SECONDARY_CONTROL_CHANNEL_BROADCAST_ABBREVIATED(121, "SECONDARY CONTROL CHANNEL BROADCAST", 9), - PHASE1_122_RFSS_STATUS_BROADCAST_ABBREVIATED(122, "RFSS STATUS BROADCAST ABBREVIATED", 9), - PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED(123, "NETWORK STATUS BROADCAST ABBREVIATED", 11), - PHASE1_124_ADJACENT_STATUS_BROADCAST_ABBREVIATED(124, "ADJACENT STATUS BROADCAST ABBREVIATED", 9), - PHASE1_125_IDENTIFIER_UPDATE(125, "IDENTIFIER UPDATE", 9), - PHASE1_128_L3HARRIS_GPS_LOCATION(128, "GPS LOCATION", -1), - PHASE1_168_L3HARRIS_TALKER_ALIAS(168, "TALKER ALIAS", -1), - PHASE1_176_L3HARRIS_GROUP_REGROUP(176, "REGROUP COMMAND", 17), - PHASE1_PARTITION_1_UNKNOWN_OPCODE(-1, "UNKNOWN PHASE 1 OPCODE", -1), - - VENDOR_PARTITION_2_UNKNOWN_OPCODE(-1, "UNKNOWN VENDOR OPCODE", -1), - - PHASE1_192_GROUP_VOICE_CHANNEL_GRANT_EXTENDED(192, "GROUP VOICE CHANNEL GRANT EXTENDED", 11), - PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT(195, "GROUP VOICE CHANNEL GRANT UPDATE EXPLICIT", 8), - PHASE1_196_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_EXTENDED(196, "UNIT-TO-UNIT VOICE CHANNEL GRANT ABBREVIATED", 15), - PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED(197, "UNIT-TO-UNIT ANSWER REQUEST EXTENDED", 12), - PHASE1_198_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED(198, "UNIT-TO-UNIT VOICE CHANNEL GRANT EXTENDED", 15), - PHASE1_204_RADIO_UNIT_MONITOR_COMMAND_EXTENDED(204, "RADIO UNIT MONITOR COMMAND EXTENDED", 14), - PHASE1_214_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT(214, "SNDCP DATA CHANNEL ANNOUNCEMENT EXPLICIT", 9), - PHASE1_216_STATUS_UPDATE_EXTENDED(216, "STATUS UPDATE EXTENDED", 14), - PHASE1_218_STATUS_QUERY_EXTENDED(218, "STATUS QUERY EXTENDED", 11), - PHASE1_220_MESSAGE_UPDATE_EXTENDED(220, "MESSAGE UPDATE EXTENDED", 14), - PHASE1_223_CALL_ALERT_EXTENDED(223, "CALL ALERT EXTENDED", 11), - PHASE1_228_EXTENDED_FUNCTION_COMMAND_EXTENDED(228, "EXTENDED FUNCTION COMMAND EXTENDED", 14), - PHASE1_233_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT(233, "SECONDARY CONTROL CHANNEL BROADCAST EXPLICIT", 8), - PHASE1_234_GROUP_AFFILIATION_QUERY_EXTENDED(234, "GROUP AFFILIATION QUERY EXTENDED", 11), - PHASE1_250_RFSS_STATUS_BROADCAST_EXTENDED(250, "RFSS STATUS BROADCAST EXTENDED", 11), - PHASE1_251_NETWORK_STATUS_BROADCAST_EXTENDED(251, "NETWORK STATUS BROADCAST EXTENDED", 13), - PHASE1_252_ADJACENT_STATUS_BROADCAST_EXTENDED(252, "ADJACENT STATUS BROADCAST EXTENDED", 11), - PHASE1_EXTENDED_PARTITION_3_UNKNOWN_OPCODE(-1, "UNKNOWN EXTENDED PHASE 1 OPCODE", 1), - - UNKNOWN(-1, "UNKNOWN", -1); - - private static final Map LOOKUP_MAP = new TreeMap<>(); + PUSH_TO_TALK(Vendor.STANDARD, -1, "PUSH-TO-TALK", -1), + END_PUSH_TO_TALK(Vendor.STANDARD, -1, "END_PUSH-TO-TALK", -1), + + TDMA_00_NULL_INFORMATION_MESSAGE(Vendor.STANDARD, 0, "NULL INFORMATION", -1), + TDMA_01_GROUP_VOICE_CHANNEL_USER_ABBREVIATED(Vendor.STANDARD, 1, "GROUP VOICE CHANNEL USER ABBREVIATED", 7), + TDMA_02_UNIT_TO_UNIT_VOICE_CHANNEL_USER_ABBREVIATED(Vendor.STANDARD, 2, "UNIT-TO-UNIT VOICE CHANNEL USER ABBREVIATED", 8), + TDMA_03_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER(Vendor.STANDARD, 3, "TELEPHONE INTERCONNECT VOICE CHANNEL USER", 7), + TDMA_05_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_IMPLICIT(Vendor.STANDARD, 5, "GROUP VOICE CHANNEL GRANT UPDATE MULTIPLE", 16), + TDMA_08_NULL_AVOID_ZERO_BIAS(Vendor.STANDARD, 8, "NULL AVOID ZERO BIAS", Integer.MIN_VALUE), + TDMA_10_MULTI_FRAGMENT_CONTINUATION_MESSAGE(Vendor.STANDARD, 16, "MULTI-FRAGMENT CONTINUATION MESSAGE", Integer.MIN_VALUE), + TDMA_11_INDIRECT_GROUP_PAGING_WITHOUT_PRIORITY(Vendor.STANDARD, 17, "INDIRECT GROUP PAGING WITHOUT PRIORITY", Integer.MIN_VALUE), + TDMA_12_INDIVIDUAL_PAGING_WITH_PRIORITY(Vendor.STANDARD, 18, "INDIVIDUAL PAGING WITH PRIORITY", Integer.MIN_VALUE), + TDMA_21_GROUP_VOICE_CHANNEL_USER_EXTENDED(Vendor.STANDARD, 33, "GROUP VOICE CHANNEL USER EXTENDED", 14), + TDMA_22_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED(Vendor.STANDARD, 34, "UNIT-TO-UNIT VOICE CHANNEL USER EXTENDED", 15), + TDMA_25_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT(Vendor.STANDARD, 37, "GROUP VOICE CHANNEL GRANT UPDATE MULTIPLE EXPLICIT", 15), + TDMA_30_POWER_CONTROL_SIGNAL_QUALITY(Vendor.STANDARD, 48, "POWER CONTROL SIGNAL QUALITY", 5), + TDMA_31_MAC_RELEASE(Vendor.STANDARD, 49, "MAC RELEASE", 7), + TDMA_PARTITION_0_UNKNOWN_OPCODE(Vendor.STANDARD, -1, "UNKNOWN TDMA OPCODE", -1), + + PHASE1_40_GROUP_VOICE_CHANNEL_GRANT_IMPLICIT(Vendor.STANDARD, 64, "GROUP VOICE CHANNEL GRANT IMPLICIT", 9), + PHASE1_41_GROUP_VOICE_SERVICE_REQUEST(Vendor.STANDARD, 65, "GROUP VOICE SERVICE REQUEST", 7), + PHASE1_42_GROUP_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT(Vendor.STANDARD, 66, "GROUP VOICE CHANNEL GRANT UPDATE IMPLICIT", 9), + PHASE1_44_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_ABBREVIATED(Vendor.STANDARD, 68, "UNIT-TO-UNIT VOICE SERVICE CHANNEL GRANT ABBREVIATED", 9), + PHASE1_45_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED(Vendor.STANDARD, 69, "UNIT-TO-UNIT ANSWER REQUEST ABBREVIATED", 10), + PHASE1_46_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED(Vendor.STANDARD, 70, "UNIT-TO-UNIT VOICE CHANNEL GRANT UPDATE ABBREVIATED", 9), + PHASE1_48_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_IMPLICIT(Vendor.STANDARD, 72, "TELEPHONE INTERCONNECT VOICE CHANNEL GRANT IMPLICIT", 10), + PHASE1_49_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT(Vendor.STANDARD, 73, "TELEPHONE INTERCONNECT VOICE CHANNEL GRANT UPDATE IMPLICIT", 10), + PHASE1_4A_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE(Vendor.STANDARD, 74, "TELEPHONE INTERCONNECT_ANSWER RESPONSE", 7), + PHASE1_4C_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED(Vendor.STANDARD, 76, "RADIO UNIT MONITOR COMMAND ABBREVIATED", 10), + PHASE1_52_SNDCP_DATA_CHANNEL_REQUEST(Vendor.STANDARD, 82, "SNDCP DATA CHANNEL REQUEST", 8), + PHASE1_53_SNDCP_DATA_PAGE_RESPONSE(Vendor.STANDARD, 83, "SNDCP DATA PAGE RESPONSE", 9), + PHASE1_54_SNDCP_DATA_CHANNEL_GRANT(Vendor.STANDARD, 84, "SNDCP DATA CHANNEL GRANT", 9), + PHASE1_55_SNDCP_DATA_PAGE_REQUEST(Vendor.STANDARD, 85, "SNDCP DATA PAGE REQUEST", 7), + PHASE1_58_STATUS_UPDATE_ABBREVIATED(Vendor.STANDARD, 88, "STATUS UPDATE ABBREVIATED", 10), + PHASE1_5A_STATUS_QUERY_ABBREVIATED(Vendor.STANDARD, 90, "STATUS QUERY ABBREVIATED", 7), + PHASE1_5C_MESSAGE_UPDATE_ABBREVIATED(Vendor.STANDARD, 92, "MESSAGE UPDATE ABBREVIATED", 10), + PHASE1_5D_RADIO_UNIT_MONITOR_COMMAND_OBSOLETE(Vendor.STANDARD, 93, "RADIO UNIT MONITOR COMMAND **OBSOLETE**", 8), + PHASE1_5E_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_ABBREVIATED(Vendor.STANDARD, 94, "RADIO UNIT MONITOR ENHANCED COMMAND ABBREVIATED", 14), + PHASE1_5F_CALL_ALERT_ABBREVIATED(Vendor.STANDARD, 95, "CALL ALERT ABBREVIATED", 7), + PHASE1_60_ACKNOWLEDGE_RESPONSE_FNE_ABBREVIATED(Vendor.STANDARD, 96, "ACKNOWLEDGE RESPONSE FNE ABBREVIATED", 9), + PHASE1_61_QUEUED_RESPONSE(Vendor.STANDARD, 97, "QUEUED RESPONSE", 9), + PHASE1_64_EXTENDED_FUNCTION_COMMAND_ABBREVIATED(Vendor.STANDARD, 100, "EXTENDED FUNCTION COMMAND ABBREVIATED", 9), + PHASE1_67_DENY_RESPONSE(Vendor.STANDARD, 103, "DENY RESPONSE", 9), + PHASE1_68_GROUP_AFFILIATION_RESPONSE_ABBREVIATED(Vendor.STANDARD, 104, "GROUP AFFILIATION RESPONSE ABBREVIATED", 10), + PHASE1_6A_GROUP_AFFILIATION_QUERY_ABBREVIATED(Vendor.STANDARD, 106, "GROUP AFFILIATION QUERY ABBREVIATED", 7), + PHASE1_6B_LOCATION_REGISTRATION_RESPONSE(Vendor.STANDARD, 107, "LOCATION REGISTRATION RESPONSE", 10), + PHASE1_6C_UNIT_REGISTRATION_RESPONSE_ABBREVIATED(Vendor.STANDARD, 108, "UNIT REGISTRATION RESPONSE ABBREVIATED",10), + PHASE1_6D_UNIT_REGISTRATION_COMMAND_ABBREVIATED(Vendor.STANDARD, 109, "UNIT REGISTRATION COMMAND ABBREVIATED", 7), + PHASE1_6F_DEREGISTRATION_ACKNOWLEDGE(Vendor.STANDARD, 111, "DEREGISTRATION ACKNOWLEDGE", 9), + PHASE1_70_SYNCHRONIZATION_BROADCAST(Vendor.STANDARD, 112, "SYNCHRONIZATION BROADCAST", 9), + PHASE1_71_AUTHENTICATION_DEMAND(Vendor.STANDARD, 113, "AUTHENTICATION DEMAND", 18), //Multi-Fragment + PHASE1_72_AUTHENTICATION_FNE_RESPONSE_ABBREVIATED(Vendor.STANDARD, 114, "AUTHENTICATION FNE RESPONSE ABBREVIATED", 9), + PHASE1_73_IDENTIFIER_UPDATE_TDMA_ABBREVIATED(Vendor.STANDARD, 115, "IDENTIFIER UPDATE TDMA ABBREVIATED", 9), + PHASE1_74_IDENTIFIER_UPDATE_V_UHF(Vendor.STANDARD, 116, "IDENTIFIER UPDATE V/UHF", 9), + PHASE1_75_TIME_AND_DATE_ANNOUNCEMENT(Vendor.STANDARD, 117, "TIME AND DATE ANNOUNCEMENT", 9), + PHASE1_76_ROAMING_ADDRESS_COMMAND(Vendor.STANDARD, 118, "ROAMING ADDRESS COMMAND", 10), + PHASE1_77_ROAMING_ADDRESS_UPDATE(Vendor.STANDARD, 119, "ROAMING ADDRESS UPDATE", 13), + PHASE1_78_SYSTEM_SERVICE_BROADCAST(Vendor.STANDARD, 120, "SYSTEM SERVICE BROADCAST", 9), + PHASE1_79_SECONDARY_CONTROL_CHANNEL_BROADCAST_IMPLICIT(Vendor.STANDARD, 121, "SECONDARY CONTROL CHANNEL BROADCAST IMPLICIT", 9), + PHASE1_7A_RFSS_STATUS_BROADCAST_IMPLICIT(Vendor.STANDARD, 122, "RFSS STATUS BROADCAST IMPLICIT", 9), + PHASE1_7B_NETWORK_STATUS_BROADCAST_IMPLICIT(Vendor.STANDARD, 123, "NETWORK STATUS BROADCAST IMPLICIT", 11), + PHASE1_7C_ADJACENT_STATUS_BROADCAST_IMPLICIT(Vendor.STANDARD, 124, "ADJACENT STATUS BROADCAST IMPLICIT", 9), + PHASE1_7D_IDENTIFIER_UPDATE(Vendor.STANDARD, 125, "IDENTIFIER UPDATE", 9), + PHASE1_PARTITION_1_UNKNOWN_OPCODE(Vendor.STANDARD, -1, "UNKNOWN PHASE 1 OPCODE", -1), + + //Vendor partition opcodes using STANDARD vendor ID (observed on L3Harris systems) + PHASE1_88_UNKNOWN_LCCH_OPCODE(Vendor.STANDARD, 136, "UNKNOWN OPCODE 136", 5), + PHASE1_90_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED(Vendor.STANDARD, 144, "GROUP REGROUP VOICE CHANNEL USER ABBREVIATED", 7), + + L3HARRIS_81_UNKNOWN_OPCODE_129(Vendor.HARRIS, 129, "L3HARRIS UNKNOWN OPCODE 129", Integer.MIN_VALUE), + L3HARRIS_8F_UNKNOWN_OPCODE_143(Vendor.HARRIS, 143, "L3HARRIS UNKNOWN OPCODE 143", Integer.MIN_VALUE), + L3HARRIS_A0_PRIVATE_DATA_CHANNEL_GRANT(Vendor.HARRIS, 160, "L3HARRIS PRIVATE DATA CHANNEL GRANT", 9), + L3HARRIS_A8_TALKER_ALIAS(Vendor.HARRIS, 168, "L3HARRIS TALKER ALIAS", -1), + L3HARRIS_AA_GPS_LOCATION(Vendor.HARRIS, 170, "L3HARRIS GPS LOCATION", 17), + L3HARRIS_AC_UNIT_TO_UNIT_DATA_CHANNEL_GRANT(Vendor.HARRIS, 172, "L3HARRIS UNIT-2-UNIT DATA CHANNEL GRANT", 12), + L3HARRIS_B0_GROUP_REGROUP_EXPLICIT_ENCRYPTION_COMMAND(Vendor.HARRIS, 176, "L3HARRIS GROUP REGROUP EXPLICIT ENCRYPTION COMMAND", 17),//Variable length, but 17 in practice + + MOTOROLA_80_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED(Vendor.MOTOROLA, 128, "MOTOROLA GROUP REGROUP VOICE CHANNEL USER ABBREVIATED", 8), + MOTOROLA_81_GROUP_REGROUP_ADD(Vendor.MOTOROLA, 129, "MOTOROLA GROUP REGROUP ADD", 17), + MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA, 131, "MOTOROLA INDIVIDUAL REGROUP ACTIVE", 8), + MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND(Vendor.MOTOROLA, 133, "MOTOROLA GROUP REGROUP", 11), + MOTOROLA_89_GROUP_REGROUP_DELETE(Vendor.MOTOROLA, 137, "MOTOROLA GROUP REGROUP DELETE", 17), + //Opcode 144 uses STANDARD vendor ID for some reason. + MOTOROLA_91_GROUP_REGROUP_UNKNOWN(Vendor.MOTOROLA, 145, "MOTOROLA GROUP REGROUP UNKNOWN", 17), + MOTOROLA_95_UNKNOWN_149(Vendor.MOTOROLA, 149, "MOTOROLA UNKNOWN 149", 17), + MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED(Vendor.MOTOROLA, 160, "MOTOROLA GROUP REGROUP VOICE CHANNEL USER EXTENDED", 16), + MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT(Vendor.MOTOROLA, 163, "MOTOROLA GROUP REGROUP CHANNEL GRANT IMPLICIT", 11), + MOTOROLA_A4_GROUP_REGROUP_CHANNEL_GRANT_EXPLICIT(Vendor.MOTOROLA, 164, "MOTOROLA GROUP REGROUP CHANNEL GRANT EXPLICIT", 13), + MOTOROLA_A5_GROUP_REGROUP_CHANNEL_GRANT_UPDATE(Vendor.MOTOROLA, 165, "MOTOROLA GROUP REGROUP CHANNEL GRANT UPDATE", 11), + MOTOROLA_A6_QUEUED_RESPONSE(Vendor.MOTOROLA, 166, "MOTOROLA QUEUED RESPONSE", 11), + MOTOROLA_A7_DENY_RESPONSE(Vendor.MOTOROLA, 167, "MOTOROLA QUEUED RESPONSE", 11), + MOTOROLA_A8_ACKNOWLEDGE_RESPONSE(Vendor.MOTOROLA, 168, "MOTOROLA ACKNOWLEDGE RESPONSE", 10), + + VENDOR_PARTITION_2_UNKNOWN_OPCODE(Vendor.STANDARD, -1, "UNKNOWN VENDOR OPCODE", Integer.MIN_VALUE), //Variable length + + PHASE1_C0_GROUP_VOICE_CHANNEL_GRANT_EXPLICIT(Vendor.STANDARD, 192, "GROUP VOICE CHANNEL GRANT EXPLICIT", 11), + PHASE1_C3_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT(Vendor.STANDARD, 195, "GROUP VOICE CHANNEL GRANT UPDATE EXPLICIT", 8), + PHASE1_C4_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_VCH(Vendor.STANDARD, 196, "UNIT-TO-UNIT VOICE SERVICE CHANNEL GRANT EXTENDED VCH", 15), + PHASE1_C5_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED(Vendor.STANDARD, 197, "UNIT-TO-UNIT ANSWER REQUEST EXTENDED", 14), + PHASE1_C6_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_VCH(Vendor.STANDARD, 198, "UNIT-TO-UNIT VOICE CHANNEL GRANT UPDATE EXTENDED VCH", 15), + PHASE1_C7_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_LCCH(Vendor.STANDARD, 199, "UNIT-TO-UNIT VOICE CHANNEL GRANT UPDATE EXTENDED LCCH", 18), //Multi-Fragment + PHASE1_C8_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_EXPLICIT(Vendor.STANDARD, 200, "TELEPHONE INTERCONNECT VOICE CHANNEL GRANT EXPLICIT", 12), + PHASE1_C9_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT(Vendor.STANDARD, 201, "TELEPHONE INTERCONNECT VOICE CHANNEL GRANT UPDATE EXPLICIT", 12), + PHASE1_CB_CALL_ALERT_EXTENDED_LCCH(Vendor.STANDARD, 203, "CALL ALERT EXTENDED LCCH", 18), //Multi-Fragment + PHASE1_CC_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_VCH(Vendor.STANDARD, 204, "RADIO UNIT MONITOR COMMAND EXTENDED VCH", 14), + PHASE1_CD_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_LCCH(Vendor.STANDARD, 205, "RADIO UNIT MONITOR COMMAND EXTENDED LCCH", 18), //Multi-Fragment + PHASE1_CE_MESSAGE_UPDATE_EXTENDED_LCCH(Vendor.STANDARD, 206, "MESSAGE UPDATE EXTENDED LCCH", 18), //Multi-Fragment + PHASE1_CF_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_LCCH(Vendor.STANDARD, 207, "UNIT-TO-UNIT VOICE SERVICE GRANT EXTENDED LCCH", 18), //Multi-Fragment + PHASE1_D6_SNDCP_DATA_CHANNEL_ANNOUNCEMENT(Vendor.STANDARD, 214, "SNDCP DATA CHANNEL ANNOUNCEMENT", 9), + PHASE1_D8_STATUS_UPDATE_EXTENDED_VCH(Vendor.STANDARD, 216, "STATUS UPDATE EXTENDED VCH", 14), + PHASE1_D9_STATUS_UPDATE_EXTENDED_LCCH(Vendor.STANDARD, 217, "STATUS UPDATE EXTENDED LCCH", 18), //Multi-Fragment + PHASE1_DA_STATUS_QUERY_EXTENDED_VCH(Vendor.STANDARD, 218, "STATUS QUERY EXTENDED VCH", 11), + PHASE1_DB_STATUS_QUERY_EXTENDED_LCCH(Vendor.STANDARD, 219, "STATUS QUERY EXTENDED LCCH", 18), //Multi-Fragment + PHASE1_DC_MESSAGE_UPDATE_EXTENDED_VCH(Vendor.STANDARD, 220, "MESSAGE UPDATE EXTENDED VCH", 14), + PHASE1_DE_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_EXTENDED(Vendor.STANDARD, 222, "RADIO UNIT MONITOR ENHANCED COMMAND EXTENDED", 18), //Multi-Fragment + PHASE1_DF_CALL_ALERT_EXTENDED_VCH(Vendor.STANDARD, 223, "CALL ALERT EXTENDED VCH", 11), + PHASE1_E0_ACKNOWLEDGE_RESPONSE_FNE_EXTENDED(Vendor.STANDARD, 224, "ACKNOWLEDGE RESPONSE FNE EXTENDED", 18), //Multi-Fragment + PHASE1_E4_EXTENDED_FUNCTION_COMMAND_EXTENDED_VCH(Vendor.STANDARD, 228, "EXTENDED FUNCTION COMMAND EXTENDED VCH", 17), + PHASE1_E5_EXTENDED_FUNCTION_COMMAND_EXTENDED_LCCH(Vendor.STANDARD, 229, "EXTENDED FUNCTION COMMAND EXTENDED LCCH", 14), + PHASE1_E8_GROUP_AFFILIATION_RESPONSE_EXTENDED(Vendor.STANDARD, 232, "GROUP AFFILIATION RESPONSE EXTENDED", 16), + PHASE1_E9_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT(Vendor.STANDARD, 233, "SECONDARY CONTROL CHANNEL BROADCAST EXPLICIT", 8), + PHASE1_EA_GROUP_AFFILIATION_QUERY_EXTENDED(Vendor.STANDARD, 234, "GROUP AFFILIATION QUERY EXTENDED", 11), + PHASE1_EC_UNIT_REGISTRATION_RESPONSE_EXTENDED(Vendor.STANDARD, 236, "UNIT REGISTRATION RESPONSE EXTENDED",13), + PHASE1_F2_AUTHENTICATION_FNE_RESPONSE_EXTENDED(Vendor.STANDARD, 242, "AUTHENTICATION FNE RESPONSE EXTENDED", 16), + PHASE1_F3_IDENTIFIER_UPDATE_TDMA_EXTENDED(Vendor.STANDARD, 243, "IDENTIFIER UPDATE TDMA EXTENDED", 14), + PHASE1_FA_RFSS_STATUS_BROADCAST_EXPLICIT(Vendor.STANDARD, 250, "RFSS STATUS BROADCAST EXPLICIT", 11), + PHASE1_FB_NETWORK_STATUS_BROADCAST_EXPLICIT(Vendor.STANDARD, 251, "NETWORK STATUS BROADCAST EXPLICIT", 13), + PHASE1_FC_ADJACENT_STATUS_BROADCAST_EXPLICIT(Vendor.STANDARD, 252, "ADJACENT STATUS BROADCAST EXPLICIT", 11), + PHASE1_FE_ADJACENT_STATUS_BROADCAST_EXTENDED_EXPLICIT(Vendor.STANDARD, 254, "ADJACENT STATUS BROADCAST EXTENDED EXPLICIT", 15), + + PHASE1_EXTENDED_PARTITION_3_UNKNOWN_OPCODE(Vendor.STANDARD, -1, "UNKNOWN EXTENDED PHASE 1 OPCODE", 1), + + UNKNOWN(Vendor.STANDARD, -1, "UNKNOWN", -1); + + private static final Map STANDARD_LOOKUP_MAP = new TreeMap<>(); + private static final Map HARRIS_LOOKUP_MAP = new TreeMap<>(); + private static final Map MOTOROLA_LOOKUP_MAP = new TreeMap<>(); + private static final Logger LOGGER = LoggerFactory.getLogger(MacOpcode.class); static { @@ -112,85 +181,134 @@ public enum MacOpcode { if(macOpcode.getValue() != -1) { - LOOKUP_MAP.put(macOpcode.getValue(), macOpcode); + switch(macOpcode.getVendor()) + { + case STANDARD -> STANDARD_LOOKUP_MAP.put(macOpcode.getValue(), macOpcode); + case HARRIS -> HARRIS_LOOKUP_MAP.put(macOpcode.getValue(), macOpcode); + case MOTOROLA -> MOTOROLA_LOOKUP_MAP.put(macOpcode.getValue(), macOpcode); + default -> LOGGER.warn("P25 Phase 2 Opcode [" + macOpcode.name() + "] - unrecognized vendor: " + macOpcode.getVendor()); + } } } } + private Vendor mVendor; private int mValue; private String mLabel; private int mLength; /** * Constructor + * @param vendor for the opcode * @param value of the opcode * @param label for display * @param length of the message in bytes. */ - MacOpcode(int value, String label, int length) + MacOpcode(Vendor vendor, int value, String label, int length) { + mVendor = vendor; mValue = value; mLabel = label; mLength = length; } + /** + * Multi-Fragment opcodes that use Opcode 0x10 Multi-Fragment Continuation message for subsequent message fragments. + */ + public static EnumSet MULTI_FRAGMENT = EnumSet.of(PHASE1_71_AUTHENTICATION_DEMAND, + PHASE1_C7_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_LCCH, + PHASE1_CB_CALL_ALERT_EXTENDED_LCCH, PHASE1_CD_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_LCCH, + PHASE1_CE_MESSAGE_UPDATE_EXTENDED_LCCH, PHASE1_CF_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_LCCH, + PHASE1_D9_STATUS_UPDATE_EXTENDED_LCCH, PHASE1_DB_STATUS_QUERY_EXTENDED_LCCH, + PHASE1_DE_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_EXTENDED, PHASE1_E0_ACKNOWLEDGE_RESPONSE_FNE_EXTENDED); + /** * Call maintenance */ public static EnumSet CALL_MAINTENANCE = EnumSet.of(PUSH_TO_TALK, END_PUSH_TO_TALK, - TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED, TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER, - TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER, TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE, - TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED, TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED, - TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT, PHASE1_176_L3HARRIS_GROUP_REGROUP); + PHASE1_48_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_IMPLICIT, + PHASE1_49_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT, + PHASE1_C8_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_EXPLICIT, + TDMA_01_GROUP_VOICE_CHANNEL_USER_ABBREVIATED, TDMA_02_UNIT_TO_UNIT_VOICE_CHANNEL_USER_ABBREVIATED, + TDMA_03_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER, L3HARRIS_B0_GROUP_REGROUP_EXPLICIT_ENCRYPTION_COMMAND); /** * Data channel grants */ - public static EnumSet DATA_GRANTS = EnumSet.of(PHASE1_84_SNDCP_DATA_CHANNEL_GRANT); + public static EnumSet DATA_GRANTS = EnumSet.of(PHASE1_54_SNDCP_DATA_CHANNEL_GRANT); /** * Mobile requests and responses */ - public static EnumSet MOBILE_REQUEST_RESPONSE = EnumSet.of(PHASE1_65_GROUP_VOICE_SERVICE_REQUEST, - PHASE1_88_STATUS_UPDATE_ABBREVIATED, PHASE1_92_MESSAGE_UPDATE_ABBREVIATED, - PHASE1_216_STATUS_UPDATE_EXTENDED, PHASE1_220_MESSAGE_UPDATE_EXTENDED); + public static EnumSet MOBILE_REQUEST_RESPONSE = EnumSet.of(PHASE1_41_GROUP_VOICE_SERVICE_REQUEST, + PHASE1_58_STATUS_UPDATE_ABBREVIATED, PHASE1_5C_MESSAGE_UPDATE_ABBREVIATED, + PHASE1_D8_STATUS_UPDATE_EXTENDED_VCH, PHASE1_DC_MESSAGE_UPDATE_EXTENDED_VCH); /** * Network requests and responses */ - public static EnumSet NETWORK_REQUEST_RESPONSE = EnumSet.of(TDMA_0_NULL_INFORMATION_MESSAGE, - TDMA_17_INDIRECT_GROUP_PAGING, TDMA_18_INDIVIDUAL_PAGING_MESSAGE_WITH_PRIORITY, - TDMA_48_POWER_CONTROL_SIGNAL_QUALITY, TDMA_49_MAC_RELEASE, PHASE1_69_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED, - PHASE1_74_TELEPHONE_INTERCONNECT_ANSWER_REQUEST, PHASE1_76_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED, - PHASE1_85_SNDCP_DATA_PAGE_REQUEST, PHASE1_90_STATUS_QUERY_ABBREVIATED, - OBSOLETE_PHASE1_93_RADIO_UNIT_MONITOR_COMMAND, PHASE1_94_RADIO_UNIT_MONITOR_COMMAND_ENHANCED, - PHASE1_95_CALL_ALERT_ABBREVIATED, PHASE1_96_ACK_RESPONSE, PHASE1_97_QUEUED_RESPONSE, - PHASE1_100_EXTENDED_FUNCTION_COMMAND_ABBREVIATED, PHASE1_103_DENY_RESPONSE, - PHASE1_106_GROUP_AFFILIATION_QUERY_ABBREVIATED, PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED, - PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED, PHASE1_204_RADIO_UNIT_MONITOR_COMMAND_EXTENDED, - PHASE1_218_STATUS_QUERY_EXTENDED, PHASE1_223_CALL_ALERT_EXTENDED, - PHASE1_228_EXTENDED_FUNCTION_COMMAND_EXTENDED, PHASE1_234_GROUP_AFFILIATION_QUERY_EXTENDED); + public static EnumSet NETWORK_REQUEST_RESPONSE = EnumSet.of(TDMA_00_NULL_INFORMATION_MESSAGE, + TDMA_11_INDIRECT_GROUP_PAGING_WITHOUT_PRIORITY, TDMA_12_INDIVIDUAL_PAGING_WITH_PRIORITY, + TDMA_30_POWER_CONTROL_SIGNAL_QUALITY, TDMA_31_MAC_RELEASE, PHASE1_45_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED, + PHASE1_4A_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE, PHASE1_4C_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED, + PHASE1_52_SNDCP_DATA_CHANNEL_REQUEST, PHASE1_53_SNDCP_DATA_PAGE_RESPONSE, PHASE1_55_SNDCP_DATA_PAGE_REQUEST, + PHASE1_5A_STATUS_QUERY_ABBREVIATED, + PHASE1_5D_RADIO_UNIT_MONITOR_COMMAND_OBSOLETE, PHASE1_5E_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_ABBREVIATED, + PHASE1_5F_CALL_ALERT_ABBREVIATED, PHASE1_60_ACKNOWLEDGE_RESPONSE_FNE_ABBREVIATED, PHASE1_61_QUEUED_RESPONSE, + PHASE1_68_GROUP_AFFILIATION_RESPONSE_ABBREVIATED, + PHASE1_64_EXTENDED_FUNCTION_COMMAND_ABBREVIATED, PHASE1_67_DENY_RESPONSE, + PHASE1_6A_GROUP_AFFILIATION_QUERY_ABBREVIATED, PHASE1_6B_LOCATION_REGISTRATION_RESPONSE, + PHASE1_6C_UNIT_REGISTRATION_RESPONSE_ABBREVIATED, PHASE1_6D_UNIT_REGISTRATION_COMMAND_ABBREVIATED, + PHASE1_6F_DEREGISTRATION_ACKNOWLEDGE, PHASE1_71_AUTHENTICATION_DEMAND, + PHASE1_72_AUTHENTICATION_FNE_RESPONSE_ABBREVIATED, PHASE1_76_ROAMING_ADDRESS_COMMAND, + PHASE1_C5_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED, + PHASE1_E8_GROUP_AFFILIATION_RESPONSE_EXTENDED, PHASE1_CC_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_VCH, + PHASE1_DA_STATUS_QUERY_EXTENDED_VCH, PHASE1_DF_CALL_ALERT_EXTENDED_VCH, + PHASE1_E4_EXTENDED_FUNCTION_COMMAND_EXTENDED_VCH, PHASE1_EA_GROUP_AFFILIATION_QUERY_EXTENDED, + PHASE1_F2_AUTHENTICATION_FNE_RESPONSE_EXTENDED); /** * Network and channel status and announcements */ - public static EnumSet NETWORK_STATUS = EnumSet.of(PHASE1_115_IDENTIFIER_UPDATE_TDMA, - PHASE1_116_IDENTIFIER_UPDATE_V_UHF, PHASE1_117_TIME_AND_DATE_ANNOUNCEMENT, - PHASE1_120_SYSTEM_SERVICE_BROADCAST, PHASE1_121_SECONDARY_CONTROL_CHANNEL_BROADCAST_ABBREVIATED, - PHASE1_122_RFSS_STATUS_BROADCAST_ABBREVIATED, PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED, - PHASE1_124_ADJACENT_STATUS_BROADCAST_ABBREVIATED, PHASE1_125_IDENTIFIER_UPDATE, - PHASE1_214_SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT, PHASE1_233_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT, - PHASE1_250_RFSS_STATUS_BROADCAST_EXTENDED, PHASE1_251_NETWORK_STATUS_BROADCAST_EXTENDED, - PHASE1_252_ADJACENT_STATUS_BROADCAST_EXTENDED); + public static EnumSet NETWORK_STATUS = EnumSet.of(PHASE1_70_SYNCHRONIZATION_BROADCAST, + PHASE1_73_IDENTIFIER_UPDATE_TDMA_ABBREVIATED, + PHASE1_74_IDENTIFIER_UPDATE_V_UHF, PHASE1_75_TIME_AND_DATE_ANNOUNCEMENT, + PHASE1_78_SYSTEM_SERVICE_BROADCAST, PHASE1_79_SECONDARY_CONTROL_CHANNEL_BROADCAST_IMPLICIT, + PHASE1_7A_RFSS_STATUS_BROADCAST_IMPLICIT, PHASE1_7B_NETWORK_STATUS_BROADCAST_IMPLICIT, + PHASE1_7C_ADJACENT_STATUS_BROADCAST_IMPLICIT, PHASE1_7D_IDENTIFIER_UPDATE, + PHASE1_D6_SNDCP_DATA_CHANNEL_ANNOUNCEMENT, PHASE1_E9_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT, + PHASE1_FA_RFSS_STATUS_BROADCAST_EXPLICIT, PHASE1_FB_NETWORK_STATUS_BROADCAST_EXPLICIT, + PHASE1_FC_ADJACENT_STATUS_BROADCAST_EXPLICIT); /** * Voice channel grants */ - public static EnumSet VOICE_GRANTS = EnumSet.of(PHASE1_64_GROUP_VOICE_CHANNEL_GRANT_ABBREVIATED, - PHASE1_66_GROUP_VOICE_CHANNEL_GRANT_UPDATE, PHASE1_68_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_ABBREVIATED, - PHASE1_70_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED, PHASE1_192_GROUP_VOICE_CHANNEL_GRANT_EXTENDED, - PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT, PHASE1_196_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_EXTENDED, - PHASE1_198_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED); + public static EnumSet VOICE_GRANTS = EnumSet.of(TDMA_05_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_IMPLICIT, + TDMA_21_GROUP_VOICE_CHANNEL_USER_EXTENDED, TDMA_22_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED, + TDMA_25_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT, PHASE1_40_GROUP_VOICE_CHANNEL_GRANT_IMPLICIT, + PHASE1_42_GROUP_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT, PHASE1_44_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_ABBREVIATED, + PHASE1_46_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED, PHASE1_C0_GROUP_VOICE_CHANNEL_GRANT_EXPLICIT, + PHASE1_C3_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT, PHASE1_C4_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_VCH, + PHASE1_C6_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_VCH); + + + /** + * Indicates if this is a multi-fragment mac structure opcode, meaning that there is a base message that is followed + * by one or more Opcode 0x10 MultiFragmentContinuationMessage mac structures. + */ + public boolean isMultiFragment() + { + return MULTI_FRAGMENT.contains(this); + } + + /** + * Indicates if this opcode is an SNDCP data channel grant opcode + */ + public boolean isDataChannelGrant() + { + return DATA_GRANTS.contains(this); + } + /** * Indicates if the enumeration element is contained in one of the enumset groupings above. @@ -209,6 +327,14 @@ public String toString() return mLabel; } + /** + * Vendor for the opcode. + */ + public Vendor getVendor() + { + return mVendor; + } + public int getValue() { return mValue; @@ -230,12 +356,28 @@ public boolean isVariableLength() return getLength() == Integer.MIN_VALUE; } + /** + * Indicates if this opcode has an unknown length. + */ + public boolean isUnknownLength() + { + return !isVariableLength() && getLength() < 0; + } + + /** + * Indicates if this opcode falls in the vendor/custom opcode partition. + */ + public boolean isVendorPartition() + { + return (128 <= getValue() && getValue() <= 191) || this.equals(VENDOR_PARTITION_2_UNKNOWN_OPCODE); + } + /** * Lookup the MAC opcode from an integer value */ public static MacOpcode fromValue(int value) { - MacOpcode mapValue = LOOKUP_MAP.get(value); + MacOpcode mapValue = STANDARD_LOOKUP_MAP.get(value); if (mapValue != null) { return mapValue; @@ -260,4 +402,38 @@ else if(192 <= value && value <= 255) return UNKNOWN; } + + /** + * Lookup opcode from value and vendor. + * @param value of the opcode + * @param vendor specified in the mac message + * @return vendor specific opcode or standard opcode equivalent if one is not found. + */ + public static MacOpcode fromValue(int value, Vendor vendor) + { + switch(vendor) + { + case HARRIS: + if(HARRIS_LOOKUP_MAP.containsKey(value)) + { + return HARRIS_LOOKUP_MAP.get(value); + } + else + { + return fromValue(value); + } + case MOTOROLA: + if(MOTOROLA_LOOKUP_MAP.containsKey(value)) + { + return MOTOROLA_LOOKUP_MAP.get(value); + } + else + { + return fromValue(value); + } + case STANDARD: + default: + return fromValue(value); + } + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacPduType.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacPduType.java index 244f99f51..e2ae5d64a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacPduType.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacPduType.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -24,7 +24,7 @@ */ public enum MacPduType { - MAC_0_RESERVED("RESERVED-0"), + MAC_0_SIGNAL("SIGNAL"), MAC_1_PTT("PUSH-TO-TALK"), MAC_2_END_PTT("END PUSH-TO-TALK"), MAC_3_IDLE("IDLE"), diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacStructure.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacStructure.java deleted file mode 100644 index c2751dd29..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacStructure.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; - -import java.util.List; - -/** - * Structure parsing parent class for MAC message payload structures. - */ -public abstract class MacStructure -{ - private static int[] OPCODE = {0, 1, 2, 3, 4, 5, 6, 7}; - - private CorrectedBinaryMessage mMessage; - private int mOffset; - - /** - * Constructs a MAC structure parser - * - * @param message containing a MAC structure - * @param offset in the message to the start of the structure - */ - protected MacStructure(CorrectedBinaryMessage message, int offset) - { - mMessage = message; - mOffset = offset; - } - - /** - * List of identifiers provided by this structure - */ - public abstract List getIdentifiers(); - - /** - * Underlying binary message. - * - * @return message - */ - protected CorrectedBinaryMessage getMessage() - { - return mMessage; - } - - /** - * Offset to the start of the structure in the underlying binary message - * - * @return offset - */ - protected int getOffset() - { - return mOffset; - } - - /** - * Opcode for the message argument - * @param message containing a mac opcode message - * @param offset into the message - * @return opcode - */ - public static MacOpcode getOpcode(CorrectedBinaryMessage message, int offset) - { - return MacOpcode.fromValue(message.getInt(OPCODE, offset)); - } - - /** - * Numeric value of the opcode - * @param message containing a mac opcode message - * @param offset into the message to the start of the mac sequence - * @return integer value - */ - public static int getOpcodeNumber(CorrectedBinaryMessage message, int offset) - { - return message.getInt(OPCODE, offset); - } - - /** - * Opcode for this message - */ - public MacOpcode getOpcode() - { - return getOpcode(getMessage(), getOffset()); - } - - /** - * Opcode numeric value for this structure - */ - public int getOpcodeNumber() - { - return getOpcodeNumber(getMessage(), getOffset()); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/UnknownMacMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/UnknownMacMessage.java index 77fdf791a..58d4cea82 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/UnknownMacMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/UnknownMacMessage.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac; @@ -25,8 +22,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownStructure; - +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownMacStructure; import java.util.Collections; import java.util.List; @@ -44,7 +40,7 @@ public class UnknownMacMessage extends MacMessage */ public UnknownMacMessage(int timeslot, DataUnitID dataUnitID, CorrectedBinaryMessage message, long timestamp) { - super(timeslot, dataUnitID, message, timestamp, new UnknownStructure(message, 0)); + super(timeslot, dataUnitID, message, timestamp, new UnknownMacStructure(message, 0)); } @Override @@ -54,15 +50,14 @@ public String toString() sb.append("TS").append(getTimeslot()); sb.append(" ").append(getDataUnitID()); - if(isValid()) - { - sb.append(" ").append(getMacStructure().toString()); - } - else + if(!isValid()) { - sb.append(" INVALID/CRC ERROR"); + sb.append(" [CRC ERROR]"); } + sb.append(" ").append(getMacPduType().toString()); + sb.append(" ").append(getMacStructure().toString()); + return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AcknowledgeResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AcknowledgeResponseFNEAbbreviated.java similarity index 55% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AcknowledgeResponse.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AcknowledgeResponseFNEAbbreviated.java index 4259899d2..1f6ab2032 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AcknowledgeResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AcknowledgeResponseFNEAbbreviated.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -20,37 +20,29 @@ package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; /** - * Acknowledge response + * Acknowledge response FNE abbreviated */ -public class AcknowledgeResponse extends MacStructure +public class AcknowledgeResponseFNEAbbreviated extends MacStructure { private static final int ADDITIONAL_INFORMATION_INDICATOR = 8; private static final int EXTENDED_ADDRESS = 9; - private static final int[] SERVICE_TYPE = {10, 11, 12, 13, 14, 15}; - - private static final int[] TARGET_WACN = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35}; - private static final int[] TARGET_SYSTEM = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - private static final int[] SOURCE_ADDRESS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47}; - + private static final IntField SERVICE_TYPE = IntField.range(10, 15); + private static final IntField TARGET_WACN = IntField.range(16, 35); + private static final IntField TARGET_SYSTEM = IntField.range(36, 48); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_48); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_4_BIT_24); + private List mIdentifiers; - private Identifier mTargetAddress; - private Identifier mTargetWacn; - private Identifier mTargetSystem; private Identifier mSourceAddress; /** @@ -59,7 +51,7 @@ public class AcknowledgeResponse extends MacStructure * @param message containing the message bits * @param offset into the message for this structure */ - public AcknowledgeResponse(CorrectedBinaryMessage message, int offset) + public AcknowledgeResponseFNEAbbreviated(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -71,23 +63,18 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getOpcode()); - sb.append(" TO:").append(getTargetAddress()); - if(hasAdditionalInformation()) + if(getTargetAddress() != null) { - if(hasExtendedAddressing()) - { - sb.append(" WACN:").append(getTargetWacn()); - sb.append(" SYS:").append(getTargetSystem()); - } - else - { - sb.append(" FM:").append(getSourceAddress()); - } + sb.append(" TO:").append(getTargetAddress()); } - sb.append(" ACKNOWLEDGING:").append(getServiceType().toString()); + if(getSourceAddress() != null) + { + sb.append(" FM:").append(getSourceAddress()); + } + sb.append(" ACKNOWLEDGING:").append(getServiceType()).append(" OPCODE:").append(getInt(SERVICE_TYPE)); return sb.toString(); } @@ -112,7 +99,7 @@ public boolean hasExtendedAddressing() */ public MacOpcode getServiceType() { - return MacOpcode.fromValue(getMessage().getInt(SERVICE_TYPE, getOffset())); + return MacOpcode.fromValue(getInt(SERVICE_TYPE)); } /** @@ -122,30 +109,21 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - public Identifier getTargetWacn() - { - if(hasAdditionalInformation() && hasExtendedAddressing() && mTargetWacn == null) - { - mTargetWacn = APCO25Wacn.create(getMessage().getInt(TARGET_WACN, getOffset())); - } - - return mTargetWacn; - } + int address = getInt(TARGET_ADDRESS); - public Identifier getTargetSystem() - { - if(hasAdditionalInformation() && hasExtendedAddressing() && mTargetSystem == null) - { - mTargetSystem = APCO25Wacn.create(getMessage().getInt(TARGET_SYSTEM, getOffset())); + if(hasAdditionalInformation() && hasExtendedAddressing()) + { + int wacn = getInt(TARGET_WACN); + int system = getInt(TARGET_SYSTEM); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, address); + } + else + { + mTargetAddress = APCO25RadioIdentifier.createTo(address); + } } - return mTargetSystem; + return mTargetAddress; } /** @@ -155,7 +133,7 @@ public Identifier getSourceAddress() { if(hasAdditionalInformation() && !hasExtendedAddressing() && mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; @@ -167,19 +145,13 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - - if(hasAdditionalInformation()) + if(getTargetAddress() != null) + { + mIdentifiers.add(getTargetAddress()); + } + if(getSourceAddress() != null) { - if(hasExtendedAddressing()) - { - mIdentifiers.add(getTargetWacn()); - mIdentifiers.add(getTargetSystem()); - } - else - { - mIdentifiers.add(getSourceAddress()); - } + mIdentifiers.add(getSourceAddress()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AcknowledgeResponseFNEExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AcknowledgeResponseFNEExtended.java new file mode 100644 index 000000000..28ff4c38f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AcknowledgeResponseFNEExtended.java @@ -0,0 +1,141 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; +import java.util.ArrayList; +import java.util.List; + +/** + * Acknowledge response FNE extended + */ +public class AcknowledgeResponseFNEExtended extends MacStructureMultiFragment +{ + private static final IntField SERVICE_TYPE = IntField.range(26, 31); + private static final IntField SOURCE_SUID_WACN = IntField.range(OCTET_5_BIT_32, OCTET_5_BIT_32 + 20); + private static final IntField SOURCE_SUID_SYSTEM = IntField.range(52, 63); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_9_BIT_64); + private static final IntField TARGET_SUID_WACN = IntField.range(OCTET_12_BIT_88, OCTET_12_BIT_88 + 20); + private static final IntField TARGET_SUID_SYSTEM = IntField.range(108, 119); + private static final IntField TARGET_SUID_ID = IntField.length24(OCTET_16_BIT_120); + private static final IntField FRAGMENT_0_SOURCE_ADDRESS = IntField.length24(OCTET_3_BIT_16); + private static final IntField FRAGMENT_0_TARGET_ADDRESS = IntField.length24(OCTET_6_BIT_40); + + private Identifier mSourceAddress; + private Identifier mTargetAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public AcknowledgeResponseFNEExtended(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + + if(getSourceAddress() != null) + { + sb.append(" FM:").append(getSourceAddress()); + } + + sb.append(" ACKNOWLEDGING:").append(getServiceType()).append(" OPCODE:").append(getInt(SERVICE_TYPE)); + return sb.toString(); + } + + /** + * The service type opcode associated with the acknowledgment + */ + public MacOpcode getServiceType() + { + return MacOpcode.fromValue(getInt(SERVICE_TYPE)); + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasFragment(0)) + { + int address = getFragment(0).getInt(FRAGMENT_0_TARGET_ADDRESS); + int wacn = getInt(TARGET_SUID_WACN); + int system = getInt(TARGET_SUID_SYSTEM); + int id = getInt(TARGET_SUID_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetAddress; + } + + /** + * From Radio Unit + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null && hasFragment(0)) + { + int address = getFragment(0).getInt(FRAGMENT_0_SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + //Note: this has to be dynamically constructed each time to account for late-add continuation fragments. + List identifiers = new ArrayList<>(); + + if(getSourceAddress() != null) + { + identifiers.add(getSourceAddress()); + } + + if(getTargetAddress() != null) + { + identifiers.add(getTargetAddress()); + } + + return identifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastAbbreviated.java deleted file mode 100644 index 0207c6fc7..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastAbbreviated.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; -import io.github.dsheirer.module.decode.p25.identifier.APCO25System; -import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Adjacent (neighbor site) status broadcast - abbreviated format - */ -public class AdjacentStatusBroadcastAbbreviated extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int CONVENTIONAL_CHANNEL_FLAG = 16; - private static final int SITE_FAILURE_FLAG = 17; - private static final int VALID_INFORMATION_FLAG = 18; - private static final int ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG = 19; - private static final int[] SYSTEM_ID = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - - private static final int[] RFSS_ID = {32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SITE_ID = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; - - private List mIdentifiers; - private Identifier mLRA; - private List mSiteFlags; - private Identifier mSystem; - private Identifier mRFSS; - private Identifier mSite; - private APCO25Channel mChannel; - private SystemServiceClass mSystemServiceClass; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public AdjacentStatusBroadcastAbbreviated(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" SYSTEM:").append(getSystem()); - sb.append(" RFSS:").append(getRFSS()); - sb.append(" SITE:").append(getSite()); - sb.append(" LRA:").append(getLRA()); - sb.append(" CHANNEL:").append(getChannel()); - sb.append(" FLAGS:").append(getSiteFlags()); - sb.append(" SERVICES:").append(getSystemServiceClass().getServices()); - return sb.toString(); - } - - public Identifier getLRA() - { - if(mLRA == null) - { - mLRA = APCO25Lra.create(getMessage().getInt(LRA, getOffset())); - } - - return mLRA; - } - - public List getSiteFlags() - { - if(mSiteFlags == null) - { - mSiteFlags = new ArrayList<>(); - - if(isConventionalChannel()) - { - mSiteFlags.add("CONVENTIONAL CHANNEL"); - } - - if(isFailedConditionSite()) - { - mSiteFlags.add("FAILURE CONDITION"); - } - - if(isValidSiteInformation()) - { - mSiteFlags.add("VALID INFORMATION"); - } - - if(isActiveNetworkConnectionToRfssControllerSite()) - { - mSiteFlags.add("ACTIVE RFSS CONNECTION"); - } - } - - return mSiteFlags; - } - - /** - * Indicates if the channel is a conventional repeater channel - */ - public boolean isConventionalChannel() - { - return getMessage().get(CONVENTIONAL_CHANNEL_FLAG + getOffset()); - } - - /** - * Indicates if the site is in a failure condition - */ - public boolean isFailedConditionSite() - { - return getMessage().get(SITE_FAILURE_FLAG + getOffset()); - } - - /** - * Indicates if the site informaiton is valid - */ - public boolean isValidSiteInformation() - { - return getMessage().get(VALID_INFORMATION_FLAG + getOffset()); - } - - /** - * Indicates if the site has an active network connection to the RFSS controller - */ - public boolean isActiveNetworkConnectionToRfssControllerSite() - { - return getMessage().get(ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG + getOffset()); - } - - public Identifier getRFSS() - { - if(mRFSS == null) - { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS_ID, getOffset())); - } - - return mRFSS; - } - - public Identifier getSite() - { - if(mSite == null) - { - mSite = APCO25Site.create(getMessage().getInt(SITE_ID, getOffset())); - } - - return mSite; - } - - public Identifier getSystem() - { - if(mSystem == null) - { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID, getOffset())); - } - - return mSystem; - } - - public APCO25Channel getChannel() - { - if(mChannel == null) - { - mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND, getOffset()), - getMessage().getInt(CHANNEL_NUMBER, getOffset())); - } - - return mChannel; - } - - public SystemServiceClass getSystemServiceClass() - { - if(mSystemServiceClass == null) - { - mSystemServiceClass = SystemServiceClass.create(getMessage().getInt(SERVICE_CLASS, getOffset())); - } - - return mSystemServiceClass; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getLRA()); - mIdentifiers.add(getSystem()); - mIdentifiers.add(getRFSS()); - mIdentifiers.add(getSite()); - mIdentifiers.add(getChannel()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - return Collections.singletonList(getChannel()); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastExplicit.java similarity index 50% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastExtended.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastExplicit.java index 81cf8be2d..865c1c5d7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastExplicit.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; @@ -30,32 +28,29 @@ import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; import io.github.dsheirer.module.decode.p25.identifier.APCO25System; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.identifier.channel.P25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; +import io.github.dsheirer.module.decode.p25.reference.SiteFlags; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.Collections; import java.util.List; /** - * RFSS status broadcast - extended format + * Adjacent (neighbor site) status broadcast explicit */ -public class RfssStatusBroadcastExtended extends MacStructure implements IFrequencyBandReceiver +public class AdjacentStatusBroadcastExplicit extends MacStructure implements IFrequencyBandReceiver { - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int R = 18; - private static final int A = 19; - private static final int[] SYSTEM_ID = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - - private static final int[] RFSS_ID = {32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SITE_ID = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] TRANSMIT_FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] RECEIVE_FREQUENCY_BAND = {64, 65, 66, 67}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] SERVICE_CLASS = {80, 81, 82, 83, 84, 85, 86, 87}; + private static final IntField LRA = IntField.length8(OCTET_2_BIT_8); + private static final IntField SITE_FLAGS = IntField.length4(OCTET_3_BIT_16); + private static final IntField SYSTEM_ID = IntField.length12(OCTET_3_BIT_16 + 4); + private static final IntField RFSS_ID = IntField.length8(OCTET_5_BIT_32); + private static final IntField SITE_ID = IntField.length8(OCTET_6_BIT_40); + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_48); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_9_BIT_64); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_9_BIT_64 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_11_BIT_80); private List mIdentifiers; private Identifier mLRA; @@ -63,6 +58,7 @@ public class RfssStatusBroadcastExtended extends MacStructure implements IFreque private Identifier mRFSS; private Identifier mSite; private APCO25Channel mChannel; + private SiteFlags mSiteFlags; private SystemServiceClass mSystemServiceClass; /** @@ -71,7 +67,7 @@ public class RfssStatusBroadcastExtended extends MacStructure implements IFreque * @param message containing the message bits * @param offset into the message for this structure */ - public RfssStatusBroadcastExtended(CorrectedBinaryMessage message, int offset) + public AdjacentStatusBroadcastExplicit(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -88,6 +84,7 @@ public String toString() sb.append(" SITE:").append(getSite()); sb.append(" LRA:").append(getLRA()); sb.append(" CHANNEL:").append(getChannel()); + sb.append(" FLAGS:").append(getSiteFlags()); sb.append(" SERVICES:").append(getSystemServiceClass().getServices()); return sb.toString(); } @@ -96,17 +93,27 @@ public Identifier getLRA() { if(mLRA == null) { - mLRA = APCO25Lra.create(getMessage().getInt(LRA, getOffset())); + mLRA = APCO25Lra.create(getInt(LRA)); } return mLRA; } + public SiteFlags getSiteFlags() + { + if(mSiteFlags == null) + { + mSiteFlags = SiteFlags.create(getInt(SITE_FLAGS)); + } + + return mSiteFlags; + } + public Identifier getRFSS() { if(mRFSS == null) { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS_ID, getOffset())); + mRFSS = APCO25Rfss.create(getInt(RFSS_ID)); } return mRFSS; @@ -116,7 +123,7 @@ public Identifier getSite() { if(mSite == null) { - mSite = APCO25Site.create(getMessage().getInt(SITE_ID, getOffset())); + mSite = APCO25Site.create(getInt(SITE_ID)); } return mSite; @@ -126,24 +133,18 @@ public Identifier getSystem() { if(mSystem == null) { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID, getOffset())); + mSystem = APCO25System.create(getInt(SYSTEM_ID)); } return mSystem; } - /** - * Control channel. This will be a phase 1 channel, even though it's being broadcast on a Phase II channel. - */ public APCO25Channel getChannel() { if(mChannel == null) { - P25ExplicitChannel channel = new P25ExplicitChannel(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset())); - mChannel = new APCO25Channel(channel); + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); } return mChannel; @@ -153,7 +154,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = SystemServiceClass.create(getMessage().getInt(SERVICE_CLASS, getOffset())); + mSystemServiceClass = SystemServiceClass.create(getInt(SERVICE_CLASS)); } return mSystemServiceClass; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastExtended.java deleted file mode 100644 index 844daff39..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastExtended.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; -import io.github.dsheirer.module.decode.p25.identifier.APCO25System; -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.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Adjacent (neighbor site) status broadcast - abbreviated format - */ -public class AdjacentStatusBroadcastExtended extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int CONVENTIONAL_CHANNEL_FLAG = 16; - private static final int SITE_FAILURE_FLAG = 17; - private static final int VALID_INFORMATION_FLAG = 18; - private static final int ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG = 19; - private static final int[] SYSTEM_ID = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - - private static final int[] RFSS_ID = {32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SITE_ID = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] TRANSMIT_FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] RECEIVE_FREQUENCY_BAND = {64, 65, 66, 67}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] SERVICE_CLASS = {80, 81, 82, 83, 84, 85, 86, 87}; - - private List mIdentifiers; - private Identifier mLRA; - private List mSiteFlags; - private Identifier mSystem; - private Identifier mRFSS; - private Identifier mSite; - private APCO25Channel mChannel; - private SystemServiceClass mSystemServiceClass; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public AdjacentStatusBroadcastExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" SYSTEM:").append(getSystem()); - sb.append(" RFSS:").append(getRFSS()); - sb.append(" SITE:").append(getSite()); - sb.append(" LRA:").append(getLRA()); - sb.append(" CHANNEL:").append(getChannel()); - sb.append(" FLAGS:").append(getSiteFlags()); - sb.append(" SERVICES:").append(getSystemServiceClass().getServices()); - return sb.toString(); - } - - public Identifier getLRA() - { - if(mLRA == null) - { - mLRA = APCO25Lra.create(getMessage().getInt(LRA, getOffset())); - } - - return mLRA; - } - - public List getSiteFlags() - { - if(mSiteFlags == null) - { - mSiteFlags = new ArrayList<>(); - - if(isConventionalChannel()) - { - mSiteFlags.add("CONVENTIONAL CHANNEL"); - } - - if(isFailedConditionSite()) - { - mSiteFlags.add("FAILURE CONDITION"); - } - - if(isValidSiteInformation()) - { - mSiteFlags.add("VALID INFORMATION"); - } - - if(isActiveNetworkConnectionToRfssControllerSite()) - { - mSiteFlags.add("ACTIVE RFSS CONNECTION"); - } - } - - return mSiteFlags; - } - - /** - * Indicates if the channel is a conventional repeater channel - */ - public boolean isConventionalChannel() - { - return getMessage().get(CONVENTIONAL_CHANNEL_FLAG + getOffset()); - } - - /** - * Indicates if the site is in a failure condition - */ - public boolean isFailedConditionSite() - { - return getMessage().get(SITE_FAILURE_FLAG + getOffset()); - } - - /** - * Indicates if the site informaiton is valid - */ - public boolean isValidSiteInformation() - { - return getMessage().get(VALID_INFORMATION_FLAG + getOffset()); - } - - /** - * Indicates if the site has an active network connection to the RFSS controller - */ - public boolean isActiveNetworkConnectionToRfssControllerSite() - { - return getMessage().get(ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG + getOffset()); - } - - public Identifier getRFSS() - { - if(mRFSS == null) - { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS_ID, getOffset())); - } - - return mRFSS; - } - - public Identifier getSite() - { - if(mSite == null) - { - mSite = APCO25Site.create(getMessage().getInt(SITE_ID, getOffset())); - } - - return mSite; - } - - public Identifier getSystem() - { - if(mSystem == null) - { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID, getOffset())); - } - - return mSystem; - } - - public APCO25Channel getChannel() - { - if(mChannel == null) - { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset())); - } - - return mChannel; - } - - public SystemServiceClass getSystemServiceClass() - { - if(mSystemServiceClass == null) - { - mSystemServiceClass = SystemServiceClass.create(getMessage().getInt(SERVICE_CLASS, getOffset())); - } - - return mSystemServiceClass; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getLRA()); - mIdentifiers.add(getSystem()); - mIdentifiers.add(getRFSS()); - mIdentifiers.add(getSite()); - mIdentifiers.add(getChannel()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - return Collections.singletonList(getChannel()); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastExtendedExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastExtendedExplicit.java new file mode 100644 index 000000000..e6523e87a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastExtendedExplicit.java @@ -0,0 +1,200 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +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.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.reference.SiteFlags; +import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Adjacent (neighbor site) status broadcast extended explicit + */ +public class AdjacentStatusBroadcastExtendedExplicit extends MacStructure implements IFrequencyBandReceiver +{ + private static final IntField LRA = IntField.length8(OCTET_3_BIT_16); + private static final IntField SITE_FLAGS = IntField.length4(OCTET_4_BIT_24); + private static final IntField SYSTEM_ID = IntField.length12(OCTET_4_BIT_24 + 4); + private static final IntField RFSS_ID = IntField.length8(OCTET_6_BIT_40); + private static final IntField SITE_ID = IntField.length8(OCTET_7_BIT_48); + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_8_BIT_56); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_8_BIT_56 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_10_BIT_72); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_10_BIT_72 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_12_BIT_88); + private static final IntField WACN = IntField.length20(OCTET_13_BIT_96); + + private List mIdentifiers; + private Identifier mLRA; + private Identifier mSystem; + private Identifier mRFSS; + private IntegerIdentifier mSite; + private Identifier mWACN; + private APCO25Channel mChannel; + private SiteFlags mSiteFlags; + private SystemServiceClass mSystemServiceClass; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public AdjacentStatusBroadcastExtendedExplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" WACN:").append(getWACN()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" RFSS:").append(getRFSS()); + sb.append(" SITE:").append(getSite()); + sb.append(" LRA:").append(getLRA()); + sb.append(" CHANNEL:").append(getChannel()); + sb.append(" FLAGS:").append(getSiteFlags()); + sb.append(" SERVICES:").append(getSystemServiceClass().getServices()); + return sb.toString(); + } + + public Identifier getWACN() + { + if(mWACN == null) + { + mWACN = APCO25Wacn.create(getInt(WACN)); + } + + return mWACN; + } + + public Identifier getLRA() + { + if(mLRA == null) + { + mLRA = APCO25Lra.create(getInt(LRA)); + } + + return mLRA; + } + + public SiteFlags getSiteFlags() + { + if(mSiteFlags == null) + { + mSiteFlags = SiteFlags.create(getInt(SITE_FLAGS)); + } + + return mSiteFlags; + } + + public Identifier getRFSS() + { + if(mRFSS == null) + { + mRFSS = APCO25Rfss.create(getInt(RFSS_ID)); + } + + return mRFSS; + } + + public IntegerIdentifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getInt(SITE_ID)); + } + + return mSite; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getInt(SYSTEM_ID)); + } + + return mSystem; + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); + } + + return mChannel; + } + + public SystemServiceClass getSystemServiceClass() + { + if(mSystemServiceClass == null) + { + mSystemServiceClass = SystemServiceClass.create(getInt(SERVICE_CLASS)); + } + + return mSystemServiceClass; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getWACN()); + mIdentifiers.add(getLRA()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getRFSS()); + mIdentifiers.add(getSite()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastImplicit.java similarity index 53% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastAbbreviated.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastImplicit.java index 13e7cc9c1..73a53fcc0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AdjacentStatusBroadcastImplicit.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; @@ -30,30 +28,26 @@ import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; import io.github.dsheirer.module.decode.p25.identifier.APCO25System; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.identifier.channel.P25Channel; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; +import io.github.dsheirer.module.decode.p25.reference.SiteFlags; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.Collections; import java.util.List; /** - * RFSS status broadcast - abbreviated format + * Adjacent (neighbor site) status broadcast implicit */ -public class RfssStatusBroadcastAbbreviated extends MacStructure implements IFrequencyBandReceiver +public class AdjacentStatusBroadcastImplicit extends MacStructure implements IFrequencyBandReceiver { - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int R = 18; - private static final int A = 19; - private static final int[] SYSTEM_ID = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - - private static final int[] RFSS_ID = {32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SITE_ID = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final IntField LRA = IntField.length8(OCTET_2_BIT_8); + private static final IntField SITE_FLAGS = IntField.length4(OCTET_3_BIT_16); + private static final IntField SYSTEM_ID = IntField.length12(OCTET_3_BIT_16 + 4); + private static final IntField RFSS_ID = IntField.length8(OCTET_5_BIT_32); + private static final IntField SITE_ID = IntField.length8(OCTET_6_BIT_40); + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_48); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_9_BIT_64); private List mIdentifiers; private Identifier mLRA; @@ -61,6 +55,7 @@ public class RfssStatusBroadcastAbbreviated extends MacStructure implements IFre private Identifier mRFSS; private Identifier mSite; private APCO25Channel mChannel; + private SiteFlags mSiteFlags; private SystemServiceClass mSystemServiceClass; /** @@ -69,7 +64,7 @@ public class RfssStatusBroadcastAbbreviated extends MacStructure implements IFre * @param message containing the message bits * @param offset into the message for this structure */ - public RfssStatusBroadcastAbbreviated(CorrectedBinaryMessage message, int offset) + public AdjacentStatusBroadcastImplicit(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -86,6 +81,7 @@ public String toString() sb.append(" SITE:").append(getSite()); sb.append(" LRA:").append(getLRA()); sb.append(" CHANNEL:").append(getChannel()); + sb.append(" FLAGS:").append(getSiteFlags()); sb.append(" SERVICES:").append(getSystemServiceClass().getServices()); return sb.toString(); } @@ -94,17 +90,27 @@ public Identifier getLRA() { if(mLRA == null) { - mLRA = APCO25Lra.create(getMessage().getInt(LRA, getOffset())); + mLRA = APCO25Lra.create(getInt(LRA)); } return mLRA; } + public SiteFlags getSiteFlags() + { + if(mSiteFlags == null) + { + mSiteFlags = SiteFlags.create(getInt(SITE_FLAGS)); + } + + return mSiteFlags; + } + public Identifier getRFSS() { if(mRFSS == null) { - mRFSS = APCO25Rfss.create(getMessage().getInt(RFSS_ID, getOffset())); + mRFSS = APCO25Rfss.create(getInt(RFSS_ID)); } return mRFSS; @@ -114,7 +120,7 @@ public Identifier getSite() { if(mSite == null) { - mSite = APCO25Site.create(getMessage().getInt(SITE_ID, getOffset())); + mSite = APCO25Site.create(getInt(SITE_ID)); } return mSite; @@ -124,22 +130,17 @@ public Identifier getSystem() { if(mSystem == null) { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID, getOffset())); + mSystem = APCO25System.create(getInt(SYSTEM_ID)); } return mSystem; } - /** - * Control channel. This will be a phase 1 control channel even though it's being broadcast on a Phase 2 channel. - */ public APCO25Channel getChannel() { if(mChannel == null) { - P25Channel channel = new P25Channel(getMessage().getInt(FREQUENCY_BAND, getOffset()), - getMessage().getInt(CHANNEL_NUMBER, getOffset())); - mChannel = new APCO25Channel(channel); + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); } return mChannel; @@ -149,7 +150,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = SystemServiceClass.create(getMessage().getInt(SERVICE_CLASS, getOffset())); + mSystemServiceClass = SystemServiceClass.create(getInt(SERVICE_CLASS)); } return mSystemServiceClass; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AuthenticationDemand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AuthenticationDemand.java new file mode 100644 index 000000000..9f468c099 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AuthenticationDemand.java @@ -0,0 +1,147 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Authentication demand + */ +public class AuthenticationDemand extends MacStructureMultiFragment +{ + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_4_BIT_24); + private static final IntField TARGET_SUID_WACN = IntField.length20(OCTET_7_BIT_48); + private static final IntField TARGET_SUID_SYSTEM = IntField.length12(OCTET_9_BIT_64 + 4); + private static final IntField TARGET_SUID_ID = IntField.length24(OCTET_11_BIT_80); + private static final IntField RS_1 = IntField.length8(OCTET_14_BIT_104); + private static final IntField RS_2 = IntField.length8(OCTET_15_BIT_112); + private static final IntField RS_3 = IntField.length8(OCTET_16_BIT_120); + private static final IntField RS_4 = IntField.length8(OCTET_17_BIT_128); + private static final IntField RS_5 = IntField.length8(OCTET_18_BIT_136); + private static final IntField FRAGMENT_0_RS_6 = IntField.length8(OCTET_3_BIT_16); + private static final IntField FRAGMENT_0_RS_7 = IntField.length8(OCTET_4_BIT_24); + private static final IntField FRAGMENT_0_RS_8 = IntField.length8(OCTET_5_BIT_32); + private static final IntField FRAGMENT_0_RS_9 = IntField.length8(OCTET_6_BIT_40); + private static final IntField FRAGMENT_0_RS_10 = IntField.length8(OCTET_7_BIT_48); + private static final IntField FRAGMENT_0_RAND1_1 = IntField.length8(OCTET_8_BIT_56); + private static final IntField FRAGMENT_0_RAND1_2 = IntField.length8(OCTET_9_BIT_64); + private static final IntField FRAGMENT_0_RAND1_3 = IntField.length8(OCTET_10_BIT_72); + private static final IntField FRAGMENT_0_RAND1_4 = IntField.length8(OCTET_11_BIT_80); + private static final IntField FRAGMENT_0_RAND1_5 = IntField.length8(OCTET_12_BIT_88); + private List mIdentifiers; + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public AuthenticationDemand(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + String challenge = getChallenge(); + if(challenge != null) + { + sb.append(" CHALLENGE:").append(challenge); + } + sb.append(" RANDOM SEED:").append(getRandomSeed()); + return sb.toString(); + } + + public String getChallenge() + { + if(hasFragment(0)) + { + StringBuilder sb = new StringBuilder(); + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RAND1_1))); + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RAND1_2))); + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RAND1_3))); + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RAND1_4))); + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RAND1_5))); + return sb.toString(); + } + + return null; + } + + public String getRandomSeed() + { + StringBuilder sb = new StringBuilder(); + sb.append(formatOctetAsHex(getInt(RS_1))); + sb.append(formatOctetAsHex(getInt(RS_2))); + sb.append(formatOctetAsHex(getInt(RS_3))); + sb.append(formatOctetAsHex(getInt(RS_4))); + sb.append(formatOctetAsHex(getInt(RS_5))); + + if(hasFragment(0)) + { + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RS_6))); + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RS_7))); + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RS_8))); + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RS_9))); + sb.append(formatOctetAsHex(getFragment(0).getInt(FRAGMENT_0_RS_10))); + } + + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + int address = getInt(TARGET_ADDRESS); + int wacn = getInt(TARGET_SUID_WACN); + int system = getInt(TARGET_SUID_SYSTEM); + int id = getInt((TARGET_SUID_ID)); + + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AuthenticationFNEResponseAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AuthenticationFNEResponseAbbreviated.java new file mode 100644 index 000000000..148de0b6c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AuthenticationFNEResponseAbbreviated.java @@ -0,0 +1,96 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Authentication FNE response abbreviated + */ +public class AuthenticationFNEResponseAbbreviated extends MacStructure +{ + private static final IntField RES2_1 = IntField.length8(OCTET_3_BIT_16); + private static final IntField RES2_2 = IntField.length8(OCTET_4_BIT_24); + private static final IntField RES2_3 = IntField.length8(OCTET_5_BIT_32); + private static final IntField RES2_4 = IntField.length8(OCTET_6_BIT_40); + private static final IntField TARGET_ID = IntField.length24(OCTET_7_BIT_48); + private List mIdentifiers; + private Identifier mTargetId; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public AuthenticationFNEResponseAbbreviated(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetId()); + sb.append(" RESPONSE:").append(getResponse()); + return sb.toString(); + } + + public String getResponse() + { + StringBuilder sb = new StringBuilder(); + sb.append(formatOctetAsHex(getInt(RES2_1))); + sb.append(formatOctetAsHex(getInt(RES2_2))); + sb.append(formatOctetAsHex(getInt(RES2_3))); + sb.append(formatOctetAsHex(getInt(RES2_4))); + return sb.toString(); + } + + public Identifier getTargetId() + { + if(mTargetId == null) + { + mTargetId = APCO25RadioIdentifier.createTo(getInt(TARGET_ID)); + } + + return mTargetId; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetId()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AuthenticationFNEResponseExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AuthenticationFNEResponseExtended.java new file mode 100644 index 000000000..9f1c50281 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/AuthenticationFNEResponseExtended.java @@ -0,0 +1,103 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Authentication FNE response extended + */ +public class AuthenticationFNEResponseExtended extends MacStructure +{ + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_16); + private static final IntField TARGET_SUID_WACN = IntField.length20(OCTET_6_BIT_40); + private static final IntField TARGET_SUID_SYSTEM = IntField.length12(OCTET_8_BIT_56 + 4); + private static final IntField TARGET_SUID_ID = IntField.length24(OCTET_10_BIT_72); + private static final IntField RES2_1 = IntField.length8(OCTET_13_BIT_96); + private static final IntField RES2_2 = IntField.length8(OCTET_14_BIT_104); + private static final IntField RES2_3 = IntField.length8(OCTET_15_BIT_112); + private static final IntField RES2_4 = IntField.length8(OCTET_16_BIT_120); + private List mIdentifiers; + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public AuthenticationFNEResponseExtended(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" RESPONSE:").append(getResponse()); + return sb.toString(); + } + + public String getResponse() + { + StringBuilder sb = new StringBuilder(); + sb.append(formatOctetAsHex(getInt(RES2_1))); + sb.append(formatOctetAsHex(getInt(RES2_2))); + sb.append(formatOctetAsHex(getInt(RES2_3))); + sb.append(formatOctetAsHex(getInt(RES2_4))); + return sb.toString(); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + int address = getInt(TARGET_ADDRESS); + int wacn = getInt(TARGET_SUID_WACN); + int system = getInt(TARGET_SUID_SYSTEM); + int id = getInt(TARGET_SUID_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertAbbreviated.java index 890eacf41..67f4161e0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertAbbreviated.java @@ -1,45 +1,38 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; /** - * Call alert - abbreviated format + * Call alert abbreviated */ public class CallAlertAbbreviated extends MacStructure { - private static final int[] TARGET_ADDRESS = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_2_BIT_8); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_5_BIT_32); private List mIdentifiers; private Identifier mTargetAddress; private Identifier mSourceAddress; @@ -74,7 +67,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -87,7 +80,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertExtended.java deleted file mode 100644 index f14cc34d0..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertExtended.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; -import java.util.List; - -/** - * Call alert - extended format - */ -public class CallAlertExtended extends MacStructure -{ - private static final int[] TARGET_ADDRESS = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_WACN = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51}; - private static final int[] SOURCE_SYSTEM = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SOURCE_ADDRESS = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87}; - - private List mIdentifiers; - private Identifier mTargetAddress; - private Identifier mSourceSuid; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public CallAlertExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceSuid()); - sb.append(" TO:").append(getTargetAddress()); - return sb.toString(); - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - /** - * From Radio Unit - */ - public Identifier getSourceSuid() - { - if(mSourceSuid == null) - { - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(getMessage().getInt(SOURCE_WACN, getOffset()), - getMessage().getInt(SOURCE_SYSTEM, getOffset()), getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceSuid; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceSuid()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertExtendedLCCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertExtendedLCCH.java new file mode 100644 index 000000000..fa9b5a727 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertExtendedLCCH.java @@ -0,0 +1,126 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Call alert extended LCCH + */ +public class CallAlertExtendedLCCH extends MacStructureMultiFragment +{ + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_4_BIT_24); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_7_BIT_48); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_7_BIT_48 + 20); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_11_BIT_80); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_14_BIT_104); + private static final IntField TARGET_SUID_WACN = IntField.length16(OCTET_17_BIT_128); + private static final IntField FRAGMENT_0_TARGET_SUID_WACN = IntField.length4(OCTET_3_BIT_16); + private static final IntField FRAGMENT_0_TARGET_SUID_SYSTEM = IntField.length12(OCTET_3_BIT_16 + 4); + private static final IntField FRAGMENT_0_TARGET_SUID_ID = IntField.length4(OCTET_5_BIT_32); + private APCO25FullyQualifiedRadioIdentifier mTargetSUID; + private APCO25FullyQualifiedRadioIdentifier mSourceSUID; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public CallAlertExtendedLCCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceSUID()); + if(getTargetSUID() != null) + { + sb.append(" TO:").append(getTargetSUID()); + } + return sb.toString(); + } + + /** + * To Radio SUID + */ + public Identifier getTargetSUID() + { + if(mTargetSUID == null && hasFragment(0)) + { + int address = getInt(TARGET_ADDRESS); + int wacn = getInt(TARGET_SUID_WACN); + wacn <<= 4; + wacn += getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_WACN); + int system = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_SYSTEM); + int id = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_ID); + mTargetSUID = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetSUID; + } + + /** + * From Radio SUID + */ + public Identifier getSourceSUID() + { + if(mSourceSUID == null) + { + int address = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSourceSUID = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mSourceSUID; + } + + @Override + public List getIdentifiers() + { + //Note: this has to be dynamically constructed each time to account for late-add continuation fragments. + List identifiers = new ArrayList<>(); + + if(getSourceSUID() != null) + { + identifiers.add(getSourceSUID()); + } + + if(getTargetSUID() != null) + { + identifiers.add(getTargetSUID()); + } + + return identifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertExtendedVCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertExtendedVCH.java new file mode 100644 index 000000000..2747abb2c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/CallAlertExtendedVCH.java @@ -0,0 +1,109 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Call alert extended VCH + */ +public class CallAlertExtendedVCH extends MacStructure +{ + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_2_BIT_8); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_5_BIT_32); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_5_BIT_32 + 20); + private static final IntField SOURCE_SUID_ADDRESS = IntField.length24(OCTET_9_BIT_64); + + private List mIdentifiers; + private Identifier mTargetAddress; + private Identifier mSourceSuid; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public CallAlertExtendedVCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceSuid()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * From Radio Unit + */ + public Identifier getSourceSuid() + { + if(mSourceSuid == null) + { + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int radio = getInt(SOURCE_SUID_ADDRESS); + //Fully qualified, but not aliased - reuse the address as the persona. + mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(radio, wacn, system, radio); + } + + return mSourceSuid; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceSuid()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/DateAndTimeAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/DateAndTimeAnnouncement.java deleted file mode 100644 index d0c02274d..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/DateAndTimeAnnouncement.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.Collections; -import java.util.List; - -/** - * Date and time announcement - * - * NOTE: no ICD - parser is incomplete - */ -public class DateAndTimeAnnouncement extends MacStructure -{ - private static final int VD_FLAG = 8; - private static final int VT_FLAG = 9; - private static final int VL_FLAG = 10; - private static final int[] LOCAL_TIME_OFFSET = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] DATE = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47}; - private static final int[] TIME = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, - 68, 69, 70, 71}; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public DateAndTimeAnnouncement(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" DATE:").append(getMessage().getInt(DATE, getOffset())); - sb.append(" TIME:").append(getMessage().getInt(TIME, getOffset())); - return sb.toString(); - } - - @Override - public List getIdentifiers() - { - return Collections.emptyList(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/DenyResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/DenyResponse.java index 2a72916ec..d7ba61fbd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/DenyResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/DenyResponse.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -20,12 +20,11 @@ package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.reference.DenyReason; - import java.util.ArrayList; import java.util.List; @@ -35,12 +34,10 @@ public class DenyResponse extends MacStructure { private static final int ADDITIONAL_INFORMATION_INDICATOR = 8; - private static final int[] SERVICE_TYPE = {10, 11, 12, 13, 14, 15}; - private static final int[] REASON = {24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] ADDITIONAL_INFO = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, - 74, 75, 76, 77, 78, 79}; + private static final IntField SERVICE_TYPE = IntField.range(10, 15); + private static final IntField REASON = IntField.length8(OCTET_3_BIT_16); + private static final IntField ADDITIONAL_INFO = IntField.length24(OCTET_4_BIT_24); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_48); private DenyReason mDenyReason; private String mAdditionalInfo; @@ -86,8 +83,7 @@ public String getAdditionalInfo() { if(mAdditionalInfo == null) { - int arguments = getMessage().getInt(ADDITIONAL_INFO, getOffset()); - mAdditionalInfo = Integer.toHexString(arguments).toUpperCase(); + mAdditionalInfo = Integer.toHexString(getInt(ADDITIONAL_INFO)).toUpperCase(); } return mAdditionalInfo; @@ -98,14 +94,14 @@ public String getAdditionalInfo() */ public MacOpcode getDeniedServiceType() { - return MacOpcode.fromValue(getMessage().getInt(SERVICE_TYPE, getOffset())); + return MacOpcode.fromValue(getInt(SERVICE_TYPE)); } public DenyReason getDenyReason() { if(mDenyReason == null) { - mDenyReason = DenyReason.fromCode(getMessage().getInt(REASON, getOffset())); + mDenyReason = DenyReason.fromCode(getInt(REASON)); } return mDenyReason; @@ -115,7 +111,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/EndPushToTalk.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/EndPushToTalk.java index 8499d2a24..d3e2dea05 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/EndPushToTalk.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/EndPushToTalk.java @@ -1,35 +1,31 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Nac; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; @@ -41,10 +37,9 @@ public class EndPushToTalk extends MacStructure { private static final int SYSTEM_CONTROLLER = 16777215; - private static int[] COLOR_CODE = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static int[] SOURCE_ADDRESS = {104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, - 119, 120, 121, 122, 123, 124, 125, 126, 127}; - private static int[] GROUP_ADDRESS = {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; + private static final IntField COLOR_CODE = IntField.length12(OCTET_2_BIT_8 + 4); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_14_BIT_104); + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_17_BIT_128); private Identifier mColorCode; private Identifier mSourceAddress; @@ -87,7 +82,7 @@ public Identifier getNAC() { if(mColorCode == null) { - mColorCode = APCO25Nac.create(getMessage().getInt(COLOR_CODE, getOffset())); + mColorCode = APCO25Nac.create(getInt(COLOR_CODE)); } return mColorCode; @@ -100,7 +95,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; @@ -113,7 +108,7 @@ public Identifier getGroupAddress() { if(mGroupAddress == null) { - mGroupAddress = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS, getOffset())); + mGroupAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); } return mGroupAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommand.java deleted file mode 100644 index d3578c033..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommand.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; - -import java.util.ArrayList; -import java.util.List; - -/** - * Extended function command - */ -public class ExtendedFunctionCommand extends MacStructure -{ - private static final int[] FUNCTION = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] ARGUMENTS = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, - 43, 44, 45, 46, 47}; - private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - - private ExtendedFunction mExtendedFunction; - private String mArguments; - private Identifier mTargetAddress; - private List mIdentifiers; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public ExtendedFunctionCommand(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" TO:").append(getTargetAddress()); - sb.append(" FUNCTION:").append(getExtendedFunction()); - sb.append(" ARGUMENTS:").append(getArguments()); - return sb.toString(); - } - - public ExtendedFunction getExtendedFunction() - { - if(mExtendedFunction == null) - { - mExtendedFunction = ExtendedFunction.fromValue(getMessage().getInt(FUNCTION, getOffset())); - } - - return mExtendedFunction; - } - - public String getArguments() - { - if(mArguments == null) - { - int arguments = getMessage().getInt(ARGUMENTS, getOffset()); - mArguments = Integer.toHexString(arguments).toUpperCase(); - } - - return mArguments; - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandAbbreviated.java new file mode 100644 index 000000000..8d946def4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandAbbreviated.java @@ -0,0 +1,110 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; +import java.util.Collections; +import java.util.List; + +/** + * Extended function command abbreviated + */ +public class ExtendedFunctionCommandAbbreviated extends MacStructure +{ + private static final IntField FUNCTION = IntField.length16(OCTET_2_BIT_8); + private static final IntField ARGUMENTS = IntField.length24(OCTET_4_BIT_24); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_48); + private ExtendedFunction mExtendedFunction; + private String mArguments; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public ExtendedFunctionCommandAbbreviated(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FUNCTION:").append(getExtendedFunction()); + sb.append(getArguments()); + return sb.toString(); + } + + public ExtendedFunction getExtendedFunction() + { + if(mExtendedFunction == null) + { + mExtendedFunction = ExtendedFunction.fromValue(getInt(FUNCTION)); + } + + return mExtendedFunction; + } + + public String getArguments() + { + if(mArguments == null) + { + mArguments = Integer.toHexString(getInt(ARGUMENTS)).toUpperCase(); + } + + return mArguments; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = Collections.singletonList(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandExtended.java deleted file mode 100644 index 3d99363d8..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandExtended.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; - -import java.util.ArrayList; -import java.util.List; - -/** - * Extended function command - extended format - */ -public class ExtendedFunctionCommandExtended extends MacStructure -{ - private static final int[] FUNCTION = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] ARGUMENTS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 52, 53, 54, 55}; - private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, - 74, 75, 76, 77, 78, 79}; - private static final int[] FULLY_QUALIFIED_SOURCE_WACN = {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, - 94, 95, 96, 97, 98, 99}; - private static final int[] FULLY_QUALIFIED_SOURCE_SYSTEM = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - private static final int[] FULLY_QUALIFIED_SOURCE_ID = {112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, - 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135}; - - private ExtendedFunction mExtendedFunction; - private String mArguments; - private Identifier mTargetAddress; - private Identifier mSourceSuid; - private List mIdentifiers; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public ExtendedFunctionCommandExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceSuid()); - sb.append(" TO:").append(getTargetAddress()); - sb.append(" FUNCTION:").append(getExtendedFunction()); - sb.append(" ARGUMENTS:").append(getArguments()); - return sb.toString(); - } - - public ExtendedFunction getExtendedFunction() - { - if(mExtendedFunction == null) - { - mExtendedFunction = ExtendedFunction.fromValue(getMessage().getInt(FUNCTION, getOffset())); - } - - return mExtendedFunction; - } - - public String getArguments() - { - if(mArguments == null) - { - int arguments = getMessage().getInt(ARGUMENTS, getOffset()); - mArguments = Integer.toHexString(arguments).toUpperCase(); - } - - return mArguments; - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - public Identifier getSourceSuid() - { - if(mSourceSuid == null) - { - int wacn = getMessage().getInt(FULLY_QUALIFIED_SOURCE_WACN, getOffset()); - int system = getMessage().getInt(FULLY_QUALIFIED_SOURCE_SYSTEM, getOffset()); - int id = getMessage().getInt(FULLY_QUALIFIED_SOURCE_ID, getOffset()); - - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(wacn, system, id); - } - - return mSourceSuid; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceSuid()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandExtendedLCCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandExtendedLCCH.java new file mode 100644 index 000000000..ae95e5d14 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandExtendedLCCH.java @@ -0,0 +1,142 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; +import java.util.ArrayList; +import java.util.List; + +/** + * Extended function command extended LCCH + */ +public class ExtendedFunctionCommandExtendedLCCH extends MacStructure +{ + private static final IntField FUNCTION = IntField.length16(OCTET_3_BIT_16); + private static final IntField ARGUMENTS = IntField.length24(OCTET_5_BIT_32); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_8_BIT_56); + private static final IntField SOURCE_WACN = IntField.length20(OCTET_11_BIT_80); + private static final IntField SOURCE_SYSTEM = IntField.length12(OCTET_13_BIT_96 + 4); + + private ExtendedFunction mExtendedFunction; + private String mArguments; + private Identifier mTargetAddress; + private Identifier mSourceWacn; + private Identifier mSourceSystem; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public ExtendedFunctionCommandExtendedLCCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" WACN:").append(getSourceWacn()); + sb.append(" SYSTEM:").append(getSourceSystem()); + sb.append(" FUNCTION:").append(getExtendedFunction()); + sb.append(" ARGUMENTS:").append(getArguments()); + return sb.toString(); + } + + public ExtendedFunction getExtendedFunction() + { + if(mExtendedFunction == null) + { + mExtendedFunction = ExtendedFunction.fromValue(getInt(FUNCTION)); + } + + return mExtendedFunction; + } + + public String getArguments() + { + if(mArguments == null) + { + mArguments = Integer.toHexString(getInt(ARGUMENTS)).toUpperCase(); + } + + return mArguments; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceWacn() + { + if(mSourceWacn == null) + { + mSourceWacn = APCO25Wacn.create(getInt(SOURCE_WACN)); + } + + return mSourceWacn; + } + + public Identifier getSourceSystem() + { + if(mSourceSystem == null) + { + mSourceSystem = APCO25System.create(getInt(SOURCE_SYSTEM)); + } + + return mSourceSystem; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceWacn()); + mIdentifiers.add(getSourceSystem()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandExtendedVCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandExtendedVCH.java new file mode 100644 index 000000000..7ec6b8b29 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/ExtendedFunctionCommandExtendedVCH.java @@ -0,0 +1,133 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; +import java.util.ArrayList; +import java.util.List; + +/** + * Extended function command extended VCH + */ +public class ExtendedFunctionCommandExtendedVCH extends MacStructure +{ + private static final IntField FUNCTION = IntField.length16(OCTET_3_BIT_16); + private static final IntField ARGUMENTS = IntField.length24(OCTET_5_BIT_32); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_8_BIT_56); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_11_BIT_80); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_13_BIT_96 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_15_BIT_112); + + private ExtendedFunction mExtendedFunction; + private String mArguments; + private Identifier mTargetAddress; + private Identifier mSourceSuid; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public ExtendedFunctionCommandExtendedVCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceSuid()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FUNCTION:").append(getExtendedFunction()); + sb.append(" ARGUMENTS:").append(getArguments()); + return sb.toString(); + } + + public ExtendedFunction getExtendedFunction() + { + if(mExtendedFunction == null) + { + mExtendedFunction = ExtendedFunction.fromValue(getInt(FUNCTION)); + } + + return mExtendedFunction; + } + + public String getArguments() + { + if(mArguments == null) + { + mArguments = Integer.toHexString(getInt(ARGUMENTS)).toUpperCase(); + } + + return mArguments; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceSuid() + { + if(mSourceSuid == null) + { + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + //Fully qualified, but not aliased - reuse the ID as the address. + mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceSuid; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceSuid()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdate.java index d495190a2..1b8a9decd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdate.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -20,9 +20,10 @@ package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +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.phase1.message.IFrequencyBand; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import java.util.Collections; import java.util.List; @@ -31,13 +32,12 @@ */ public class FrequencyBandUpdate extends MacStructure implements IFrequencyBand { - private static final int[] FREQUENCY_BAND_IDENTIFIER = {8, 9, 10, 11}; - private static final int[] BANDWIDTH = {12, 13, 14, 15, 16, 17, 18, 19, 20}; + private static final IntField FREQUENCY_BAND_IDENTIFIER = IntField.length4(OCTET_2_BIT_8); + private static final IntField BANDWIDTH = IntField.range(12, 20); private static final int TRANSMIT_OFFSET_SIGN = 21; - private static final int[] TRANSMIT_OFFSET = {22, 23, 24, 25, 26, 27, 28, 29}; - private static final int[] CHANNEL_SPACING = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] BASE_FREQUENCY = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final LongField TRANSMIT_OFFSET = LongField.range(22, 29); + private static final LongField CHANNEL_SPACING = LongField.range(30, 39); + private static final LongField BASE_FREQUENCY = LongField.range(40, 71); /** * Constructs the message @@ -68,31 +68,31 @@ public String toString() @Override public int getIdentifier() { - return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER, getOffset()); + return getInt(FREQUENCY_BAND_IDENTIFIER); } @Override public long getChannelSpacing() { - return getMessage().getInt(CHANNEL_SPACING, getOffset()) * 125; + return getLong(CHANNEL_SPACING) * 125; } @Override public long getBaseFrequency() { - return getMessage().getLong(BASE_FREQUENCY, getOffset()) * 5; + return getLong(BASE_FREQUENCY) * 5; } @Override public int getBandwidth() { - return getMessage().getInt(BANDWIDTH, getOffset()) * 125; + return getInt(BANDWIDTH) * 125; } @Override public long getTransmitOffset() { - long offset = getMessage().getLong(TRANSMIT_OFFSET, getOffset()) * 250000; + long offset = getLong(TRANSMIT_OFFSET) * 250000; if(!getMessage().get(TRANSMIT_OFFSET_SIGN + getOffset())) { @@ -107,7 +107,7 @@ public long getTransmitOffset() */ public boolean hasTransmitOffset() { - return getMessage().getInt(TRANSMIT_OFFSET, getOffset()) != 0x80; + return getLong(TRANSMIT_OFFSET) != 0x80; } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateTDMA.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateTDMAAbbreviated.java similarity index 56% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateTDMA.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateTDMAAbbreviated.java index fde26820f..6a6281f10 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateTDMA.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateTDMAAbbreviated.java @@ -1,50 +1,45 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +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.phase1.message.IFrequencyBand; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.reference.ChannelType; -import org.apache.commons.math3.util.FastMath; - import java.util.Collections; import java.util.List; +import org.apache.commons.math3.util.FastMath; /** * Identifier update (frequency band) - TDMA bands */ -public class FrequencyBandUpdateTDMA extends MacStructure implements IFrequencyBand +public class FrequencyBandUpdateTDMAAbbreviated extends MacStructure implements IFrequencyBand { - private static final int[] FREQUENCY_BAND_IDENTIFIER = {8, 9, 10, 11}; - private static final int[] CHANNEL_TYPE = {12, 13, 14, 15}; + private static final IntField FREQUENCY_BAND_IDENTIFIER = IntField.length4(OCTET_2_BIT_8); + private static final IntField CHANNEL_TYPE = IntField.length4(OCTET_2_BIT_8 + 4); private static final int TRANSMIT_OFFSET_SIGN = 16; - private static final int[] TRANSMIT_OFFSET = {17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}; - private static final int[] CHANNEL_SPACING = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] BASE_FREQUENCY = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField TRANSMIT_OFFSET = IntField.range(17, 29); + private static final IntField CHANNEL_SPACING = IntField.range(30, 39); + private static final LongField BASE_FREQUENCY = LongField.length32(OCTET_6_BIT_40); private ChannelType mChannelType; /** @@ -53,7 +48,7 @@ public class FrequencyBandUpdateTDMA extends MacStructure implements IFrequencyB * @param message containing the message bits * @param offset into the message for this structure */ - public FrequencyBandUpdateTDMA(CorrectedBinaryMessage message, int offset) + public FrequencyBandUpdateTDMAAbbreviated(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -77,7 +72,7 @@ public ChannelType getChannelType() { if(mChannelType == null) { - mChannelType = ChannelType.fromValue(getMessage().getInt(CHANNEL_TYPE, getOffset())); + mChannelType = ChannelType.fromValue(getInt(CHANNEL_TYPE)); } return mChannelType; @@ -86,19 +81,19 @@ public ChannelType getChannelType() @Override public int getIdentifier() { - return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER, getOffset()); + return getInt(FREQUENCY_BAND_IDENTIFIER); } @Override public long getChannelSpacing() { - return getMessage().getInt(CHANNEL_SPACING, getOffset()) * 125; + return getInt(CHANNEL_SPACING) * 125; } @Override public long getBaseFrequency() { - return getMessage().getLong(BASE_FREQUENCY, getOffset()) * 5; + return getLong(BASE_FREQUENCY) * 5; } @Override @@ -110,7 +105,7 @@ public int getBandwidth() @Override public long getTransmitOffset() { - long offset = getMessage().getLong(TRANSMIT_OFFSET, getOffset()) * getChannelType().getBandwidth(); + long offset = (long)getInt(TRANSMIT_OFFSET) * getChannelType().getBandwidth(); if(!getMessage().get(TRANSMIT_OFFSET_SIGN + getOffset())) { @@ -125,7 +120,7 @@ public long getTransmitOffset() */ public boolean hasTransmitOffset() { - return getMessage().getInt(TRANSMIT_OFFSET, getOffset()) != 0x80; + return getInt(TRANSMIT_OFFSET) != 0x80; } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateTDMAExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateTDMAExtended.java new file mode 100644 index 000000000..62b61a9cf --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateTDMAExtended.java @@ -0,0 +1,188 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +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.IFrequencyBand; +import io.github.dsheirer.module.decode.p25.reference.ChannelType; +import java.util.Collections; +import java.util.List; +import org.apache.commons.math3.util.FastMath; + +/** + * Identifier update (frequency band) - TDMA extended + */ +public class FrequencyBandUpdateTDMAExtended extends MacStructure implements IFrequencyBand +{ + private static final IntField FREQUENCY_BAND_IDENTIFIER = IntField.length4(OCTET_3_BIT_16); + private static final IntField CHANNEL_TYPE = IntField.length4(OCTET_3_BIT_16 + 4); + private static final int TRANSMIT_OFFSET_SIGN = 24; + private static final IntField TRANSMIT_OFFSET = IntField.range(25, 37); + private static final IntField CHANNEL_SPACING = IntField.range(38, 47); + private static final LongField BASE_FREQUENCY = LongField.length32(OCTET_7_BIT_48); + private static final IntField WACN = IntField.length20(OCTET_11_BIT_80); + private static final IntField SYSTEM = IntField.length12(OCTET_13_BIT_96 + 4); + private ChannelType mChannelType; + private Identifier mWacn; + private Identifier mSystem; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public FrequencyBandUpdateTDMAExtended(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" ID:").append(getIdentifier()); + sb.append(" OFFSET:").append(getTransmitOffset()); + sb.append(" SPACING:").append(getChannelSpacing()); + sb.append(" BASE:").append(getBaseFrequency()); + sb.append(" ").append(getChannelType()); + sb.append(" WACN:").append(getWacn()); + sb.append(" SYSTEM:").append(getSystem()); + return sb.toString(); + } + + public Identifier getWacn() + { + if(mWacn == null) + { + mWacn = APCO25Wacn.create(getInt(WACN)); + } + + return mWacn; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getInt(SYSTEM)); + } + + return mSystem; + } + + public ChannelType getChannelType() + { + if(mChannelType == null) + { + mChannelType = ChannelType.fromValue(getInt(CHANNEL_TYPE)); + } + + return mChannelType; + } + + @Override + public int getIdentifier() + { + return getInt(FREQUENCY_BAND_IDENTIFIER); + } + + @Override + public long getChannelSpacing() + { + return getInt(CHANNEL_SPACING) * 125; + } + + @Override + public long getBaseFrequency() + { + return getLong(BASE_FREQUENCY) * 5; + } + + @Override + public int getBandwidth() + { + return getChannelType().getBandwidth(); + } + + @Override + public long getTransmitOffset() + { + long offset = (long)getInt(TRANSMIT_OFFSET) * getChannelType().getBandwidth(); + + if(!getMessage().get(TRANSMIT_OFFSET_SIGN + getOffset())) + { + offset *= -1; + } + + return offset; + } + + /** + * Indicates if the frequency band has a transmit option for the subscriber unit. + */ + public boolean hasTransmitOffset() + { + return getInt(TRANSMIT_OFFSET) != 0x80; + } + + @Override + public long getDownlinkFrequency(int channelNumber) + { + return getBaseFrequency() + (getChannelSpacing() * (int)(FastMath.floor(channelNumber / getTimeslotCount()))); + } + + @Override + public long getUplinkFrequency(int channelNumber) + { + if(hasTransmitOffset()) + { + return getDownlinkFrequency(channelNumber) + getTransmitOffset(); + } + + return 0; + } + + @Override + public boolean isTDMA() + { + return getChannelType().isTDMA(); + } + + @Override + public int getTimeslotCount() + { + return getChannelType().getSlotsPerCarrier(); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateVUHF.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateVUHF.java index 9228d467a..1e7267b6e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateVUHF.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdateVUHF.java @@ -1,32 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +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.phase1.message.IFrequencyBand; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.Collections; import java.util.List; @@ -35,13 +32,12 @@ */ public class FrequencyBandUpdateVUHF extends MacStructure implements IFrequencyBand { - private static final int[] FREQUENCY_BAND_IDENTIFIER = {8, 9, 10, 11}; - private static final int[] BANDWIDTH = {12, 13, 14, 15, 16, 17, 18, 19, 20}; - private static final int TRANSMIT_OFFSET_SIGN = 21; - private static final int[] TRANSMIT_OFFSET = {22, 23, 24, 25, 26, 27, 28, 29}; - private static final int[] CHANNEL_SPACING = {30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] BASE_FREQUENCY = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; + private static final IntField FREQUENCY_BAND_IDENTIFIER = IntField.length4(OCTET_2_BIT_8); + private static final IntField BANDWIDTH = IntField.length4(OCTET_2_BIT_8 + 4); + private static final int TRANSMIT_OFFSET_SIGN = OCTET_3_BIT_16; + private static final IntField TRANSMIT_OFFSET = IntField.range(17, 29); + private static final IntField CHANNEL_SPACING = IntField.range(30, 39); + private static final LongField BASE_FREQUENCY = LongField.length32(OCTET_6_BIT_40); /** * Constructs the message @@ -72,25 +68,25 @@ public String toString() @Override public int getIdentifier() { - return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER, getOffset()); + return getInt(FREQUENCY_BAND_IDENTIFIER); } @Override public long getChannelSpacing() { - return getMessage().getInt(CHANNEL_SPACING, getOffset()) * 125; + return getInt(CHANNEL_SPACING) * 125; } @Override public long getBaseFrequency() { - return getMessage().getLong(BASE_FREQUENCY, getOffset()) * 5; + return getLong(BASE_FREQUENCY) * 5; //Units of 5 Hz } @Override public int getBandwidth() { - int bandwidth = getMessage().getInt(BANDWIDTH, getOffset()); + int bandwidth = getInt(BANDWIDTH); if(bandwidth == 0x4) { @@ -107,7 +103,7 @@ else if(bandwidth == 0x5) @Override public long getTransmitOffset() { - long offset = getMessage().getLong(TRANSMIT_OFFSET, getOffset()) * getChannelSpacing(); + long offset = getInt(TRANSMIT_OFFSET) * getChannelSpacing(); if(!getMessage().get(TRANSMIT_OFFSET_SIGN + getOffset())) { @@ -122,7 +118,7 @@ public long getTransmitOffset() */ public boolean hasTransmitOffset() { - return getMessage().getInt(TRANSMIT_OFFSET, getOffset()) != 0x80; + return getInt(TRANSMIT_OFFSET) != 0x80; } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationQueryAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationQueryAbbreviated.java index a4e5c8aff..979bcb070 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationQueryAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationQueryAbbreviated.java @@ -1,45 +1,38 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; /** - * Group affiliation query - abbreviated format + * Group affiliation query abbreviated */ public class GroupAffiliationQueryAbbreviated extends MacStructure { - private static final int[] TARGET_ADDRESS = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_2_BIT_8); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_5_BIT_32); private List mIdentifiers; private Identifier mTargetAddress; private Identifier mSourceAddress; @@ -74,7 +67,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -87,7 +80,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationQueryExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationQueryExtended.java index 83a535e9d..96b0fc307 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationQueryExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationQueryExtended.java @@ -1,49 +1,41 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; /** - * Group affiliation query - extended format + * Group affiliation query extended */ public class GroupAffiliationQueryExtended extends MacStructure { - private static final int[] TARGET_ADDRESS = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_WACN = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51}; - private static final int[] SOURCE_SYSTEM = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SOURCE_ADDRESS = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87}; - + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_2_BIT_8); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_5_BIT_32); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_9_BIT_64); private List mIdentifiers; private Identifier mTargetAddress; private Identifier mSourceSuid; @@ -91,8 +83,11 @@ public Identifier getSourceSuid() { if(mSourceSuid == null) { - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(getMessage().getInt(SOURCE_WACN, getOffset()), - getMessage().getInt(SOURCE_SYSTEM, getOffset()), getMessage().getInt(SOURCE_ADDRESS, getOffset())); + int wacn = getMessage().getInt(SOURCE_SUID_WACN, getOffset()); + int system = getMessage().getInt(SOURCE_SUID_SYSTEM, getOffset()); + int id = getMessage().getInt(SOURCE_SUID_ID, getOffset()); + //Fully qualified, but not aliased - reuse the ID as the persona. + mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } return mSourceSuid; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationResponseAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationResponseAbbreviated.java new file mode 100644 index 000000000..6019d0dbc --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationResponseAbbreviated.java @@ -0,0 +1,122 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnnouncementTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.reference.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * Group affiliation response abbreviated + */ +public class GroupAffiliationResponseAbbreviated extends MacStructure +{ + private static final int LOCAL_OR_GLOBAL_AFFILIATION_FLAG = 16; + private static final IntField RESPONSE = IntField.range(22, 23); + private static final IntField ANNOUNCEMENT_GROUP_ADDRESS = IntField.length16(OCTET_4_BIT_24); + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_6_BIT_40); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_8_BIT_56); + + private Identifier mAnnouncementGroupAddress; + private Identifier mGroupAddress; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public GroupAffiliationResponseAbbreviated(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" AFFILIATION ").append(getResponse()); + sb.append(" FOR GROUP:").append(getGroupAddress()); + sb.append(" AFFILIATION GROUP:").append(getAnnouncementGroupAddress()); + + return sb.toString(); + } + + public Response getResponse() + { + return Response.fromValue(getInt(RESPONSE)); + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getAnnouncementGroupAddress() + { + if(mAnnouncementGroupAddress == null) + { + mAnnouncementGroupAddress = APCO25AnnouncementTalkgroup.create(getInt(ANNOUNCEMENT_GROUP_ADDRESS)); + } + + return mAnnouncementGroupAddress; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getGroupAddress()); + mIdentifiers.add(getAnnouncementGroupAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationResponseExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationResponseExtended.java new file mode 100644 index 000000000..7364f4505 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupAffiliationResponseExtended.java @@ -0,0 +1,130 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25AnnouncementTalkgroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FullyQualifiedTalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.reference.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * Group affiliation response abbreviated + */ +public class GroupAffiliationResponseExtended extends MacStructure +{ + private static final int LOCAL_OR_GLOBAL_AFFILIATION_FLAG = 16; + private static final IntField RESPONSE = IntField.range(22, 23); + private static final IntField ANNOUNCEMENT_GROUP_ADDRESS = IntField.length16(OCTET_4_BIT_24); + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_6_BIT_40); + private static final IntField SOURCE_GID_WACN = IntField.length20(OCTET_8_BIT_56); + private static final IntField SOURCE_GID_SYSTEM = IntField.length12(OCTET_10_BIT_72 + 4); + private static final IntField SOURCE_GID_ID = IntField.length16(OCTET_12_BIT_88); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_14_BIT_104); + + private Identifier mAnnouncementGroupAddress; + private APCO25FullyQualifiedTalkgroupIdentifier mSourceGID; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public GroupAffiliationResponseExtended(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" AFFILIATION ").append(getResponse()); + sb.append(" FOR GROUP:").append(getSourceGID()); + sb.append(" AFFILIATION GROUP:").append(getAnnouncementGroupAddress()); + + return sb.toString(); + } + + public Response getResponse() + { + return Response.fromValue(getInt(RESPONSE)); + } + + public Identifier getSourceGID() + { + if(mSourceGID == null) + { + int address = getInt(GROUP_ADDRESS); + int wacn = getInt(SOURCE_GID_WACN); + int system = getInt(SOURCE_GID_SYSTEM); + int id = getInt(SOURCE_GID_ID); + + mSourceGID = APCO25FullyQualifiedTalkgroupIdentifier.createFrom(address, wacn, system, id); + } + + return mSourceGID; + } + + public Identifier getAnnouncementGroupAddress() + { + if(mAnnouncementGroupAddress == null) + { + mAnnouncementGroupAddress = APCO25AnnouncementTalkgroup.create(getInt(ANNOUNCEMENT_GROUP_ADDRESS)); + } + + return mAnnouncementGroupAddress; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceGID()); + mIdentifiers.add(getAnnouncementGroupAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupPagingMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupPagingMessage.java deleted file mode 100644 index 15578d2fc..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupPagingMessage.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; -import java.util.List; - -/** - * Group paging message - */ -public class GroupPagingMessage extends MacStructure -{ - private static final int[] ID_COUNT = {14, 15}; - private static final int[] GROUP_ADDRESS_1 = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] GROUP_ADDRESS_2 = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] GROUP_ADDRESS_3 = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] GROUP_ADDRESS_4 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - - private List mIdentifiers; - private Identifier mTargetAddress1; - private Identifier mTargetAddress2; - private Identifier mTargetAddress3; - private Identifier mTargetAddress4; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public GroupPagingMessage(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" GROUP1:").append(getTargetAddress1()); - - int count = getCount(); - - if(count > 1) - { - sb.append(" GROUP2:").append(getTargetAddress2()); - - if(count > 2) - { - sb.append(" GROUP3:").append(getTargetAddress3()); - - if(count > 3) - { - sb.append(" GROUP4:").append(getTargetAddress4()); - } - } - } - - return sb.toString(); - } - - public static int getIdCount(BinaryMessage message, int offset) - { - return message.getInt(ID_COUNT, offset); - } - - /** - * Number of paging target addresses contained in this message - * @return addresses count (1 - 4) - */ - public int getCount() - { - return getIdCount(getMessage(), getOffset()); - } - - /** - * Length of the individual paging message in bytes - * - * @return - */ - public static int getLength(BinaryMessage message, int offset) - { - int count = getIdCount(message, offset); - - switch(count) - { - case 1: - return 4; - case 2: - return 6; - case 3: - return 8; - case 4: - return 10; - } - - return 4; - } - - /** - * To Talkgroup 1 - */ - public Identifier getTargetAddress1() - { - if(mTargetAddress1 == null) - { - mTargetAddress1 = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_1, getOffset())); - } - - return mTargetAddress1; - } - - /** - * To Talkgroup 2 - */ - public Identifier getTargetAddress2() - { - if(mTargetAddress2 == null && getCount() >= 2) - { - mTargetAddress2 = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_2, getOffset())); - } - - return mTargetAddress2; - } - - /** - * To Talkgroup 3 - */ - public Identifier getTargetAddress3() - { - if(mTargetAddress3 == null && getCount() >= 3) - { - mTargetAddress3 = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_3, getOffset())); - } - - return mTargetAddress3; - } - - /** - * To Talkgroup 4 - */ - public Identifier getTargetAddress4() - { - if(mTargetAddress4 == null && getCount() >= 4) - { - mTargetAddress4 = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_4, getOffset())); - } - - return mTargetAddress4; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - - int count = getCount(); - - mIdentifiers.add(getTargetAddress1()); - - if(count > 1) - { - mIdentifiers.add(getTargetAddress2()); - - if(count > 2) - { - mIdentifiers.add(getTargetAddress3()); - - if(count > 3) - { - mIdentifiers.add(getTargetAddress4()); - } - } - } - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupRegroupVoiceChannelUserAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupRegroupVoiceChannelUserAbbreviated.java new file mode 100644 index 000000000..ebbd97b55 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupRegroupVoiceChannelUserAbbreviated.java @@ -0,0 +1,131 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +import java.util.ArrayList; +import java.util.List; + +/** + * Group Regroup Voice Channel User Abbreviated + */ +public class GroupRegroupVoiceChannelUserAbbreviated extends MacStructure implements IServiceOptionsProvider +{ + private static final IntField SUPERGROUP = IntField.length16(OCTET_3_BIT_16); + private static final IntField RADIO = IntField.length24(OCTET_5_BIT_32); + private List mIdentifiers; + private PatchGroupIdentifier mPatchgroup; + private RadioIdentifier mRadio; + private ServiceOptions mServiceOptions = new VoiceServiceOptions(0); + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public GroupRegroupVoiceChannelUserAbbreviated(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("GROUP REGROUP VOICE CHANNEL USER ABBREVIATED"); + sb.append(" TALKGROUP:").append(getPatchgroup()); + if(hasRadio()) + { + sb.append(" TALKER RADIO:").append(getRadio()); + } + + return sb.toString(); + } + + @Override + public ServiceOptions getServiceOptions() + { + return mServiceOptions; + } + + /** + * Talkgroup active on this channel/timeslot. + */ + public PatchGroupIdentifier getPatchgroup() + { + if(mPatchgroup == null) + { + mPatchgroup = APCO25PatchGroup.create(getInt(SUPERGROUP)); + } + + return mPatchgroup; + } + + /** + * Talker radio identifier. + */ + public RadioIdentifier getRadio() + { + if(mRadio == null) + { + mRadio = APCO25RadioIdentifier.createFrom(getInt(RADIO)); + } + + return mRadio; + } + + /** + * Indicates if this message has a non-zero radio talker identifier. + */ + public boolean hasRadio() + { + return getInt(RADIO) > 0; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchgroup()); + + if(hasRadio()) + { + mIdentifiers.add(getRadio()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantAbbreviated.java deleted file mode 100644 index c1493198d..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantAbbreviated.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.identifier.channel.P25P2Channel; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - -import java.util.ArrayList; -import java.util.List; - -/** - * Group voice channel grant - abbreviated format - */ -public class GroupVoiceChannelGrantAbbreviated extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] FREQUENCY_BAND = {16, 17, 18, 19}; - private static final int[] CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] GROUP_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - - private List mIdentifiers; - private APCO25Channel mChannel; - private Identifier mGroupAddress; - private Identifier mSourceAddress; - private VoiceServiceOptions mServiceOptions; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public GroupVoiceChannelGrantAbbreviated(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceAddress()); - sb.append(" TO:").append(getGroupAddress()); - sb.append(" CHAN:").append(getChannel()); - sb.append(" ").append(getServiceOptions()); - return sb.toString(); - } - - /** - * Voice channel service options - */ - public VoiceServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - - /** - * Channel - */ - public APCO25Channel getChannel() - { - if(mChannel == null) - { - mChannel = new APCO25Channel(new P25P2Channel(getMessage().getInt(FREQUENCY_BAND, getOffset()), - getMessage().getInt(CHANNEL_NUMBER, getOffset()))); - } - - return mChannel; - } - - /** - * To Talkgroup - */ - public Identifier getGroupAddress() - { - if(mGroupAddress == null) - { - mGroupAddress = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS, getOffset())); - } - - return mGroupAddress; - } - - /** - * From Radio Unit - */ - public Identifier getSourceAddress() - { - if(mSourceAddress == null) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceAddress; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getGroupAddress()); - mIdentifiers.add(getSourceAddress()); - mIdentifiers.add(getChannel()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantExplicit.java new file mode 100644 index 000000000..21102ef13 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantExplicit.java @@ -0,0 +1,138 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Group voice channel grant - explicit + */ +public class GroupVoiceChannelGrantExplicit extends MacStructureVoiceService + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider +{ + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.range(16, 19); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.range(20, 31); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.range(32, 35); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.range(36, 47); + private static final IntField GROUP_ADDRESS = IntField.range(48, 63); + private static final IntField SOURCE_ADDRESS = IntField.range(64, 87); + + private List mIdentifiers; + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public GroupVoiceChannelGrantExplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" CHAN:").append(getChannel()); + sb.append(" ").append(getServiceOptions()); + return sb.toString(); + } + + /** + * Channel + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * From Radio Unit + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantExtended.java deleted file mode 100644 index ffd5a689d..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantExtended.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -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.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - -import java.util.ArrayList; -import java.util.List; - -/** - * Group voice channel grant - extended format - */ -public class GroupVoiceChannelGrantExtended extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] TRANSMIT_FREQUENCY_BAND = {16, 17, 18, 19}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] RECEIVE_FREQUENCY_BAND = {32, 33, 34, 35}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] GROUP_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SOURCE_ADDRESS = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, - 82, 83, 84, 85, 86, 87}; - - private List mIdentifiers; - private APCO25Channel mChannel; - private Identifier mGroupAddress; - private Identifier mSourceAddress; - private VoiceServiceOptions mServiceOptions; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public GroupVoiceChannelGrantExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceAddress()); - sb.append(" TO:").append(getGroupAddress()); - sb.append(" CHAN:").append(getChannel()); - sb.append(" ").append(getServiceOptions()); - return sb.toString(); - } - - /** - * Voice channel service options - */ - public VoiceServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - - /** - * Channel - */ - public APCO25Channel getChannel() - { - if(mChannel == null) - { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset())); - } - - return mChannel; - } - - /** - * To Talkgroup - */ - public Identifier getGroupAddress() - { - if(mGroupAddress == null) - { - mGroupAddress = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS, getOffset())); - } - - return mGroupAddress; - } - - /** - * From Radio Unit - */ - public Identifier getSourceAddress() - { - if(mSourceAddress == null) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceAddress; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getGroupAddress()); - mIdentifiers.add(getSourceAddress()); - mIdentifiers.add(getChannel()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantImplicit.java similarity index 50% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantAbbreviated.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantImplicit.java index af59e5856..b7fcf02ec 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantImplicit.java @@ -1,50 +1,47 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.identifier.channel.P25P2Channel; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Unit-to-unit voice channel grant - abbreviated format + * Group voice channel grant implicit */ -public class UnitToUnitVoiceChannelGrantAbbreviated extends MacStructure implements IFrequencyBandReceiver +public class GroupVoiceChannelGrantImplicit extends MacStructureVoiceService + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider { - private static final int[] FREQUENCY_BAND = {8, 9, 10, 11}; - private static final int[] CHANNEL_NUMBER = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] TARGET_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 38, 39}; - private static final int[] SOURCE_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63}; + private static final IntField FREQUENCY_BAND = IntField.range(16, 19); + private static final IntField CHANNEL_NUMBER = IntField.range(20, 31); + private static final IntField GROUP_ADDRESS = IntField.range(32, 47); + private static final IntField SOURCE_ADDRESS = IntField.range(48, 71); private List mIdentifiers; private APCO25Channel mChannel; @@ -57,7 +54,7 @@ public class UnitToUnitVoiceChannelGrantAbbreviated extends MacStructure impleme * @param message containing the message bits * @param offset into the message for this structure */ - public UnitToUnitVoiceChannelGrantAbbreviated(CorrectedBinaryMessage message, int offset) + public GroupVoiceChannelGrantImplicit(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -72,6 +69,7 @@ public String toString() sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getTargetAddress()); sb.append(" CHAN:").append(getChannel()); + sb.append(" ").append(getServiceOptions()); return sb.toString(); } @@ -82,8 +80,7 @@ public APCO25Channel getChannel() { if(mChannel == null) { - mChannel = new APCO25Channel(new P25P2Channel(getMessage().getInt(FREQUENCY_BAND, getOffset()), - getMessage().getInt(CHANNEL_NUMBER, getOffset()))); + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); } return mChannel; @@ -96,7 +93,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); } return mTargetAddress; @@ -109,7 +106,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; @@ -132,8 +129,6 @@ public List getIdentifiers() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdate.java deleted file mode 100644 index 503c562ec..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdate.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.identifier.channel.P25P2Channel; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; -import java.util.List; - -/** - * Group voice channel grant update - */ -public class GroupVoiceChannelGrantUpdate extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] FREQUENCY_BAND_A = {8, 9, 10, 11}; - private static final int[] CHANNEL_NUMBER_A = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] GROUP_ADDRESS_A = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] FREQUENCY_BAND_B = {40, 41, 42, 43}; - private static final int[] CHANNEL_NUMBER_B = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] GROUP_ADDRESS_B = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - - private List mIdentifiers; - private Identifier mGroupAddressA; - private APCO25Channel mChannelA; - private Identifier mGroupAddressB; - private APCO25Channel mChannelB; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public GroupVoiceChannelGrantUpdate(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" GROUP A:").append(getGroupAddressA()); - sb.append(" CHAN-A:").append(getChannelA()); - sb.append(" GROUP B:").append(getGroupAddressB()); - sb.append(" CHAN-B:").append(getChannelB()); - return sb.toString(); - } - - public APCO25Channel getChannelA() - { - if(mChannelA == null) - { - mChannelA = new APCO25Channel(new P25P2Channel(getMessage().getInt(FREQUENCY_BAND_A, getOffset()), - getMessage().getInt(CHANNEL_NUMBER_A, getOffset()))); - } - - return mChannelA; - } - - public APCO25Channel getChannelB() - { - if(mChannelB == null) - { - mChannelB = new APCO25Channel(new P25P2Channel(getMessage().getInt(FREQUENCY_BAND_B, getOffset()), - getMessage().getInt(CHANNEL_NUMBER_B, getOffset()))); - } - - return mChannelB; - } - - - /** - * Talkgroup channel A - */ - public Identifier getGroupAddressA() - { - if(mGroupAddressA == null) - { - mGroupAddressA = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_A, getOffset())); - } - - return mGroupAddressA; - } - - /** - * Talkgroup channel B - */ - public Identifier getGroupAddressB() - { - if(mGroupAddressB == null) - { - mGroupAddressB = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_B, getOffset())); - } - - return mGroupAddressB; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getChannelA()); - mIdentifiers.add(getChannelB()); - mIdentifiers.add(getGroupAddressA()); - mIdentifiers.add(getGroupAddressB()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - List channels = new ArrayList<>(); - channels.add(getChannelA()); - channels.add(getChannelB()); - return channels; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateExplicit.java index 52b0d44dc..393035228 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateExplicit.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; @@ -30,26 +28,22 @@ import io.github.dsheirer.module.decode.p25.identifier.channel.P25P2ExplicitChannel; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** * Group voice channel grant update - explicit channel format */ -public class GroupVoiceChannelGrantUpdateExplicit extends MacStructure implements IFrequencyBandReceiver +public class GroupVoiceChannelGrantUpdateExplicit extends MacStructureVoiceService implements IFrequencyBandReceiver { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] TRANSMIT_FREQUENCY_BAND = {16, 17, 18, 19}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] RECEIVE_FREQUENCY_BAND = {32, 33, 34, 35}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] GROUP_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.range(16, 19); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.range(20, 31); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.range(32, 35); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.range(36, 47); + private static final IntField GROUP_ADDRESS = IntField.length16(48); private List mIdentifiers; - private VoiceServiceOptions mVoiceServiceOptions; private Identifier mGroupAddress; private APCO25Channel mChannel; @@ -76,24 +70,12 @@ public String toString() return sb.toString(); } - public VoiceServiceOptions getVoiceServiceOptions() - { - if(mVoiceServiceOptions == null) - { - mVoiceServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mVoiceServiceOptions; - } - public APCO25Channel getChannel() { if(mChannel == null) { - mChannel = new APCO25ExplicitChannel(new P25P2ExplicitChannel(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset()))); + mChannel = new APCO25ExplicitChannel(new P25P2ExplicitChannel(getInt(TRANSMIT_FREQUENCY_BAND), + getInt(TRANSMIT_CHANNEL_NUMBER), getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER))); } return mChannel; @@ -106,7 +88,7 @@ public Identifier getGroupAddress() { if(mGroupAddress == null) { - mGroupAddress = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS, getOffset())); + mGroupAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); } return mGroupAddress; @@ -128,8 +110,6 @@ public List getIdentifiers() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateImplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateImplicit.java new file mode 100644 index 000000000..d038a28e2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateImplicit.java @@ -0,0 +1,178 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.List; + +/** + * Group voice channel grant update - Implicit + */ +public class GroupVoiceChannelGrantUpdateImplicit extends MacStructure implements IFrequencyBandReceiver, IServiceOptionsProvider +{ + private static final IntField FREQUENCY_BAND_1 = IntField.range(8, 11); + private static final IntField CHANNEL_NUMBER_1 = IntField.range(12, 23); + private static final IntField GROUP_ADDRESS_1 = IntField.range(24, 39); + private static final IntField FREQUENCY_BAND_2 = IntField.range(40, 43); + private static final IntField CHANNEL_NUMBER_2 = IntField.range(44, 55); + private static final IntField GROUP_ADDRESS_2 = IntField.range(56, 71); + + private List mIdentifiers; + private Identifier mGroupAddress1; + private APCO25Channel mChannel1; + private Identifier mGroupAddress2; + private APCO25Channel mChannel2; + + //Empty, non-encrypted service options instance. + private ServiceOptions mServiceOptions = new VoiceServiceOptions(0); + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public GroupVoiceChannelGrantUpdateImplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" GROUP-1:").append(getGroupAddress1()); + sb.append(" CHAN-1:").append(getChannel1()); + + if(hasGroup2()) + { + sb.append(" GROUP-2:").append(getGroupAddress2()); + sb.append(" CHAN-2:").append(getChannel2()); + } + return sb.toString(); + } + + @Override + public ServiceOptions getServiceOptions() + { + return mServiceOptions; + } + + /** + * Indicates if this message contains talkgroup and channel information for a second talkgroup. + * @return + */ + public boolean hasGroup2() + { + int group2 = getInt(GROUP_ADDRESS_2); + return group2 != 0 && group2 != getInt(GROUP_ADDRESS_1); + } + + public APCO25Channel getChannel1() + { + if(mChannel1 == null) + { + mChannel1 = APCO25Channel.create(getInt(FREQUENCY_BAND_1), getInt(CHANNEL_NUMBER_1)); + } + + return mChannel1; + } + + public APCO25Channel getChannel2() + { + if(mChannel2 == null) + { + mChannel2 = APCO25Channel.create(getInt(FREQUENCY_BAND_2), getInt(CHANNEL_NUMBER_2)); + } + + return mChannel2; + } + + + /** + * Talkgroup channel A + */ + public Identifier getGroupAddress1() + { + if(mGroupAddress1 == null) + { + mGroupAddress1 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_1)); + } + + return mGroupAddress1; + } + + /** + * Talkgroup channel B + */ + public Identifier getGroupAddress2() + { + if(mGroupAddress2 == null) + { + mGroupAddress2 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_2)); + } + + return mGroupAddress2; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getChannel1()); + mIdentifiers.add(getGroupAddress1()); + if(hasGroup2()) + { + mIdentifiers.add(getChannel2()); + mIdentifiers.add(getGroupAddress2()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel1()); + + if(hasGroup2()) + { + channels.add(getChannel2()); + } + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultiple.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultiple.java deleted file mode 100644 index f8702e33b..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultiple.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.identifier.channel.P25P2Channel; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - -import java.util.ArrayList; -import java.util.List; - -/** - * Group voice channel grant update - multiple users/channels - */ -public class GroupVoiceChannelGrantUpdateMultiple extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] SERVICE_OPTIONS_A = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] FREQUENCY_BAND_A = {16, 17, 18, 19}; - private static final int[] CHANNEL_NUMBER_A = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] GROUP_ADDRESS_A = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - - private static final int[] SERVICE_OPTIONS_B = {48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] FREQUENCY_BAND_B = {56, 57, 58, 59}; - private static final int[] CHANNEL_NUMBER_B = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] GROUP_ADDRESS_B = {72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; - - private static final int[] SERVICE_OPTIONS_C = {88, 89, 90, 91, 92, 93, 94, 95}; - private static final int[] FREQUENCY_BAND_C = {96, 97, 98, 99}; - private static final int[] CHANNEL_NUMBER_C = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - private static final int[] GROUP_ADDRESS_C = {112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, - 126, 127}; - - private List mIdentifiers; - private VoiceServiceOptions mVoiceServiceOptionsA; - private Identifier mGroupAddressA; - private APCO25Channel mChannelA; - private VoiceServiceOptions mVoiceServiceOptionsB; - private Identifier mGroupAddressB; - private APCO25Channel mChannelB; - private VoiceServiceOptions mVoiceServiceOptionsC; - private Identifier mGroupAddressC; - private APCO25Channel mChannelC; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public GroupVoiceChannelGrantUpdateMultiple(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" GROUP-A:").append(getGroupAddressA()); - sb.append(" CHAN-A:").append(getChannelA()); - sb.append(" ").append(getVoiceServiceOptionsA()); - - if(hasGroupB()) - { - sb.append(" GROUP-B:").append(getGroupAddressB()); - sb.append(" CHAN-B:").append(getChannelB()); - sb.append(" ").append(getVoiceServiceOptionsB()); - } - - if(hasGroupC()) - { - sb.append(" GROUP-C:").append(getGroupAddressC()); - sb.append(" CHAN-C:").append(getChannelC()); - sb.append(" ").append(getVoiceServiceOptionsC()); - } - - return sb.toString(); - } - - /** - * Indicates if this message contains a group address B and corresponding channel. - */ - public boolean hasGroupB() - { - int groupB = getMessage().getInt(GROUP_ADDRESS_B, getOffset()); - return getMessage().getInt(GROUP_ADDRESS_A, getOffset()) != groupB && groupB != 0; - } - - /** - * Indicates if this message contains a group address C and corresponding channel. - */ - public boolean hasGroupC() - { - int groupC = getMessage().getInt(GROUP_ADDRESS_C, getOffset()); - return getMessage().getInt(GROUP_ADDRESS_A, getOffset()) != groupC && - getMessage().getInt(GROUP_ADDRESS_B, getOffset()) != groupC && groupC != 0; - } - - public VoiceServiceOptions getVoiceServiceOptionsA() - { - if(mVoiceServiceOptionsA == null) - { - mVoiceServiceOptionsA = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS_A, getOffset())); - } - - return mVoiceServiceOptionsA; - } - - public VoiceServiceOptions getVoiceServiceOptionsB() - { - if(mVoiceServiceOptionsB == null) - { - mVoiceServiceOptionsB = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS_B, getOffset())); - } - - return mVoiceServiceOptionsB; - } - - public VoiceServiceOptions getVoiceServiceOptionsC() - { - if(mVoiceServiceOptionsC == null) - { - mVoiceServiceOptionsC = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS_C, getOffset())); - } - - return mVoiceServiceOptionsC; - } - - public APCO25Channel getChannelA() - { - if(mChannelA == null) - { - mChannelA = new APCO25Channel(new P25P2Channel(getMessage().getInt(FREQUENCY_BAND_A, getOffset()), - getMessage().getInt(CHANNEL_NUMBER_A, getOffset()))); - } - - return mChannelA; - } - - public APCO25Channel getChannelB() - { - if(mChannelB == null) - { - mChannelB = new APCO25Channel(new P25P2Channel(getMessage().getInt(FREQUENCY_BAND_B, getOffset()), - getMessage().getInt(CHANNEL_NUMBER_B, getOffset()))); - } - - return mChannelB; - } - - public APCO25Channel getChannelC() - { - if(mChannelC == null) - { - mChannelC = new APCO25Channel(new P25P2Channel(getMessage().getInt(FREQUENCY_BAND_C, getOffset()), - getMessage().getInt(CHANNEL_NUMBER_C, getOffset()))); - } - - return mChannelC; - } - - /** - * Talkgroup channel A - */ - public Identifier getGroupAddressA() - { - if(mGroupAddressA == null) - { - mGroupAddressA = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_A, getOffset())); - } - - return mGroupAddressA; - } - - /** - * Talkgroup channel B - */ - public Identifier getGroupAddressB() - { - if(mGroupAddressB == null) - { - mGroupAddressB = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_B, getOffset())); - } - - return mGroupAddressB; - } - - /** - * Talkgroup channel C - */ - public Identifier getGroupAddressC() - { - if(mGroupAddressC == null) - { - mGroupAddressC = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_C, getOffset())); - } - - return mGroupAddressC; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getChannelA()); - mIdentifiers.add(getChannelB()); - mIdentifiers.add(getChannelC()); - mIdentifiers.add(getGroupAddressA()); - mIdentifiers.add(getGroupAddressB()); - mIdentifiers.add(getGroupAddressC()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - List channels = new ArrayList<>(); - channels.add(getChannelA()); - channels.add(getChannelB()); - channels.add(getChannelC()); - return channels; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultipleExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultipleExplicit.java index f55932aa3..cd4f5dc20 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultipleExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultipleExplicit.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; @@ -30,39 +28,33 @@ import io.github.dsheirer.module.decode.p25.identifier.channel.P25P2ExplicitChannel; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** * Group voice channel grant update multiple - explicit */ -public class GroupVoiceChannelGrantUpdateMultipleExplicit extends MacStructure implements IFrequencyBandReceiver +public class GroupVoiceChannelGrantUpdateMultipleExplicit extends MacStructureVoiceService implements IFrequencyBandReceiver { - private static final int[] SERVICE_OPTIONS_A = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] TRANSMIT_FREQUENCY_BAND_A = {16, 17, 18, 19}; - private static final int[] TRANSMIT_CHANNEL_NUMBER_A = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] RECEIVE_FREQUENCY_BAND_A = {32, 33, 34, 35}; - private static final int[] RECEIVE_CHANNEL_NUMBER_A = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] GROUP_ADDRESS_A = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - - private static final int[] SERVICE_OPTIONS_B = {64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] TRANSMIT_FREQUENCY_BAND_B = {72, 73, 74, 75}; - private static final int[] TRANSMIT_CHANNEL_NUMBER_B = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] RECEIVE_FREQUENCY_BAND_B = {88, 89, 90, 91}; - private static final int[] RECEIVE_CHANNEL_NUMBER_B = {92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; - private static final int[] GROUP_ADDRESS_B = {104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, - 118, 119}; + private static final IntField TRANSMIT_FREQUENCY_BAND_1 = IntField.range(16, 19); + private static final IntField TRANSMIT_CHANNEL_NUMBER_1 = IntField.range(20, 31); + private static final IntField RECEIVE_FREQUENCY_BAND_1 = IntField.range(32, 35); + private static final IntField RECEIVE_CHANNEL_NUMBER_1 = IntField.range(36, 47); + private static final IntField GROUP_ADDRESS_1 = IntField.range(48, 63); + private static final IntField SERVICE_OPTIONS_2 = IntField.range(64, 71); + private static final IntField TRANSMIT_FREQUENCY_BAND_2 = IntField.range(72, 75); + private static final IntField TRANSMIT_CHANNEL_NUMBER_2 = IntField.range(76, 87); + private static final IntField RECEIVE_FREQUENCY_BAND_2 = IntField.range(88, 91); + private static final IntField RECEIVE_CHANNEL_NUMBER_2 = IntField.range(92, 103); + private static final IntField GROUP_ADDRESS_2 = IntField.range(104, 119); private List mIdentifiers; - private VoiceServiceOptions mVoiceServiceOptionsA; - private Identifier mGroupAddressA; - private APCO25Channel mChannelA; - private VoiceServiceOptions mVoiceServiceOptionsB; - private Identifier mGroupAddressB; - private APCO25Channel mChannelB; + private Identifier mGroupAddress1; + private APCO25Channel mChannel1; + private VoiceServiceOptions mVoiceServiceOptions2; + private Identifier mGroupAddress2; + private APCO25Channel mChannel2; /** * Constructs the message @@ -82,100 +74,93 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getOpcode()); - sb.append(" GROUP-A:").append(getGroupAddressA()); - sb.append(" CHAN-A:").append(getChannelA()); - sb.append(" ").append(getVoiceServiceOptionsA()); + sb.append(" GROUP-1:").append(getGroupAddress1()); + sb.append(" CHAN-1:").append(getChannel1()); + sb.append(" ").append(getServiceOptions1()); - if(hasGroupB()) + if(hasGroup2()) { - sb.append(" GROUP-B:").append(getGroupAddressB()); - sb.append(" CHAN-B:").append(getChannelB()); - sb.append(" ").append(getVoiceServiceOptionsB()); + sb.append(" GROUP-2:").append(getGroupAddress2()); + sb.append(" CHAN-2:").append(getChannel2()); + sb.append(" ").append(getServiceOptions2()); } return sb.toString(); } /** - * Indicates if this message contains a group address B and corresponding channel. + * Indicates if this message contains a group address 2 and corresponding channel. */ - public boolean hasGroupB() + public boolean hasGroup2() { - int groupB = getMessage().getInt(GROUP_ADDRESS_B, getOffset()); - return getMessage().getInt(GROUP_ADDRESS_A, getOffset()) != groupB && groupB != 0; + int group2 = getInt(GROUP_ADDRESS_2); + return getInt(GROUP_ADDRESS_1) != group2 && group2 != 0; } - - public VoiceServiceOptions getVoiceServiceOptionsA() + /** + * Voice service options for first group call, remapped from parent getServiceOptions. + */ + public VoiceServiceOptions getServiceOptions1() { - if(mVoiceServiceOptionsA == null) - { - mVoiceServiceOptionsA = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS_A, getOffset())); - } - - return mVoiceServiceOptionsA; + return getServiceOptions(); } - public VoiceServiceOptions getVoiceServiceOptionsB() + public VoiceServiceOptions getServiceOptions2() { - if(mVoiceServiceOptionsB == null) + if(mVoiceServiceOptions2 == null) { - mVoiceServiceOptionsB = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS_B, getOffset())); + mVoiceServiceOptions2 = new VoiceServiceOptions(getInt(SERVICE_OPTIONS_2)); } - return mVoiceServiceOptionsB; + return mVoiceServiceOptions2; } - public APCO25Channel getChannelA() + public APCO25Channel getChannel1() { - if(mChannelA == null) + if(mChannel1 == null) { - mChannelA = new APCO25ExplicitChannel(new P25P2ExplicitChannel(getMessage().getInt(TRANSMIT_FREQUENCY_BAND_A, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER_A, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND_A, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER_A, getOffset()))); + mChannel1 = new APCO25ExplicitChannel(new P25P2ExplicitChannel(getInt(TRANSMIT_FREQUENCY_BAND_1), + getInt(TRANSMIT_CHANNEL_NUMBER_1), getInt(RECEIVE_FREQUENCY_BAND_1), getInt(RECEIVE_CHANNEL_NUMBER_1))); } - return mChannelA; + return mChannel1; } - public APCO25Channel getChannelB() + public APCO25Channel getChannel2() { - if(mChannelB == null) + if(mChannel2 == null) { - mChannelB = new APCO25ExplicitChannel(new P25P2ExplicitChannel(getMessage().getInt(TRANSMIT_FREQUENCY_BAND_B, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER_B, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND_B, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER_B, getOffset()))); + mChannel2 = new APCO25ExplicitChannel(new P25P2ExplicitChannel(getInt(TRANSMIT_FREQUENCY_BAND_2), + getInt(TRANSMIT_CHANNEL_NUMBER_2), getInt(RECEIVE_FREQUENCY_BAND_2), getInt(RECEIVE_CHANNEL_NUMBER_2))); } - return mChannelB; + return mChannel2; } /** * Talkgroup channel A */ - public Identifier getGroupAddressA() + public Identifier getGroupAddress1() { - if(mGroupAddressA == null) + if(mGroupAddress1 == null) { - mGroupAddressA = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_A, getOffset())); + mGroupAddress1 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_1)); } - return mGroupAddressA; + return mGroupAddress1; } /** * Talkgroup channel B */ - public Identifier getGroupAddressB() + public Identifier getGroupAddress2() { - if(mGroupAddressB == null) + if(mGroupAddress2 == null) { - mGroupAddressB = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS_B, getOffset())); + mGroupAddress2 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_2)); } - return mGroupAddressB; + return mGroupAddress2; } @Override @@ -184,10 +169,10 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getChannelA()); - mIdentifiers.add(getChannelB()); - mIdentifiers.add(getGroupAddressA()); - mIdentifiers.add(getGroupAddressB()); + mIdentifiers.add(getChannel1()); + mIdentifiers.add(getChannel2()); + mIdentifiers.add(getGroupAddress1()); + mIdentifiers.add(getGroupAddress2()); } return mIdentifiers; @@ -197,8 +182,8 @@ public List getIdentifiers() public List getChannels() { List channels = new ArrayList<>(); - channels.add(getChannelA()); - channels.add(getChannelB()); + channels.add(getChannel1()); + channels.add(getChannel2()); return channels; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultipleImplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultipleImplicit.java new file mode 100644 index 000000000..3a8039bd3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelGrantUpdateMultipleImplicit.java @@ -0,0 +1,240 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.List; + +/** + * Group voice channel grant update - multiple users/channels - Implicit + */ +public class GroupVoiceChannelGrantUpdateMultipleImplicit extends MacStructureVoiceService implements IFrequencyBandReceiver +{ + private static final IntField FREQUENCY_BAND_1 = IntField.range(16, 19); + private static final IntField CHANNEL_NUMBER_1 = IntField.range(20, 31); + private static final IntField GROUP_ADDRESS_1 = IntField.range(32, 47); + private static final IntField SERVICE_OPTIONS_2 = IntField.range(48, 55); + private static final IntField FREQUENCY_BAND_2 = IntField.range(56, 59); + private static final IntField CHANNEL_NUMBER_2 = IntField.range(60, 71); + private static final IntField GROUP_ADDRESS_2 = IntField.range(72, 87); + private static final IntField SERVICE_OPTIONS_3 = IntField.range(88, 95); + private static final IntField FREQUENCY_BAND_3 = IntField.range(96, 99); + private static final IntField CHANNEL_NUMBER_3 = IntField.range(100, 111); + private static final IntField GROUP_ADDRESS_3 = IntField.range(112, 127); + + private List mIdentifiers; + private Identifier mGroupAddress1; + private APCO25Channel mChannel1; + private VoiceServiceOptions mVoiceServiceOptions2; + private Identifier mGroupAddress2; + private APCO25Channel mChannel2; + private VoiceServiceOptions mVoiceServiceOptions3; + private Identifier mGroupAddress3; + private APCO25Channel mChannel3; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public GroupVoiceChannelGrantUpdateMultipleImplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" GROUP-1:").append(getGroupAddress1()); + sb.append(" CHAN-1:").append(getChannel1()); + sb.append(" ").append(getServiceOptions1()); + + if(hasGroup2()) + { + sb.append(" GROUP-2:").append(getGroupAddress2()); + sb.append(" CHAN-2:").append(getChannel2()); + sb.append(" ").append(getServiceOptions2()); + } + + if(hasGroup3()) + { + sb.append(" GROUP-3:").append(getGroupAddress3()); + sb.append(" CHAN-3:").append(getChannel3()); + sb.append(" ").append(getServiceOptions3()); + } + + return sb.toString(); + } + + /** + * Indicates if this message contains a group address B and corresponding channel. + */ + public boolean hasGroup2() + { + int group2 = getInt(GROUP_ADDRESS_2); + return getInt(GROUP_ADDRESS_1) != group2 && group2 != 0; + } + + /** + * Indicates if this message contains a group address C and corresponding channel. + */ + public boolean hasGroup3() + { + int group3 = getInt(GROUP_ADDRESS_3); + return getInt(GROUP_ADDRESS_1) != group3 && getInt(GROUP_ADDRESS_2) != group3 && group3 != 0; + } + + /** + * Voice Service Options for call 1 (remapped from the getServiceOptions() from parent). + */ + public VoiceServiceOptions getServiceOptions1() + { + return getServiceOptions(); + } + + public VoiceServiceOptions getServiceOptions2() + { + if(mVoiceServiceOptions2 == null) + { + mVoiceServiceOptions2 = new VoiceServiceOptions(getInt(SERVICE_OPTIONS_2)); + } + + return mVoiceServiceOptions2; + } + + public VoiceServiceOptions getServiceOptions3() + { + if(mVoiceServiceOptions3 == null) + { + mVoiceServiceOptions3 = new VoiceServiceOptions(getInt(SERVICE_OPTIONS_3)); + } + + return mVoiceServiceOptions3; + } + + public APCO25Channel getChannel1() + { + if(mChannel1 == null) + { + mChannel1 = APCO25Channel.create(getInt(FREQUENCY_BAND_1), getInt(CHANNEL_NUMBER_1)); + } + + return mChannel1; + } + + public APCO25Channel getChannel2() + { + if(mChannel2 == null) + { + mChannel2 = APCO25Channel.create(getInt(FREQUENCY_BAND_2), getInt(CHANNEL_NUMBER_2)); + } + + return mChannel2; + } + + public APCO25Channel getChannel3() + { + if(mChannel3 == null) + { + mChannel3 = APCO25Channel.create(getInt(FREQUENCY_BAND_3), getInt(CHANNEL_NUMBER_3)); + } + + return mChannel3; + } + + /** + * Talkgroup channel A + */ + public Identifier getGroupAddress1() + { + if(mGroupAddress1 == null) + { + mGroupAddress1 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_1)); + } + + return mGroupAddress1; + } + + /** + * Talkgroup channel B + */ + public Identifier getGroupAddress2() + { + if(mGroupAddress2 == null) + { + mGroupAddress2 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_2)); + } + + return mGroupAddress2; + } + + /** + * Talkgroup channel C + */ + public Identifier getGroupAddress3() + { + if(mGroupAddress3 == null) + { + mGroupAddress3 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_3)); + } + + return mGroupAddress3; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getChannel1()); + mIdentifiers.add(getChannel2()); + mIdentifiers.add(getChannel3()); + mIdentifiers.add(getGroupAddress1()); + mIdentifiers.add(getGroupAddress2()); + mIdentifiers.add(getGroupAddress3()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel1()); + channels.add(getChannel2()); + channels.add(getChannel3()); + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelUserAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelUserAbbreviated.java index bbca789b1..9efc446f9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelUserAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelUserAbbreviated.java @@ -1,51 +1,40 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** * Group voice channel user - abbreviated format */ -public class GroupVoiceChannelUserAbbreviated extends MacStructure +public class GroupVoiceChannelUserAbbreviated extends MacStructureGroupVoiceService { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] GROUP_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_5_BIT_32); private List mIdentifiers; - private Identifier mGroupAddress; private Identifier mSourceAddress; - private VoiceServiceOptions mServiceOptions; /** * Constructs the message @@ -72,42 +61,24 @@ public String toString() } /** - * Voice channel service options - */ - public VoiceServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - - /** - * To Talkgroup + * From Radio Unit */ - public Identifier getGroupAddress() + public Identifier getSourceAddress() { - if(mGroupAddress == null) + if(mSourceAddress == null) { - mGroupAddress = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } - return mGroupAddress; + return mSourceAddress; } /** - * From Radio Unit + * Indicates if the source address is non-zero. */ - public Identifier getSourceAddress() + private boolean hasSourceAddress() { - if(mSourceAddress == null) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceAddress; + return getInt(SOURCE_ADDRESS) > 0; } @Override @@ -117,7 +88,10 @@ public List getIdentifiers() { mIdentifiers = new ArrayList<>(); mIdentifiers.add(getGroupAddress()); - mIdentifiers.add(getSourceAddress()); + if(hasSourceAddress()) + { + mIdentifiers.add(getSourceAddress()); + } } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelUserExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelUserExtended.java index da6542f3e..21050c40f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelUserExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceChannelUserExtended.java @@ -1,59 +1,43 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** - * Group voice channel user - abbreviated format + * Group voice channel user - extended format */ -public class GroupVoiceChannelUserExtended extends MacStructure +public class GroupVoiceChannelUserExtended extends MacStructureGroupVoiceService { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] GROUP_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - private static final int[] FULLY_QUALIFIED_SOURCE_WACN = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, - 70, 71, 72, 73, 74, 75}; - private static final int[] FULLY_QUALIFIED_SOURCE_SYSTEM = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] FULLY_QUALIFIED_SOURCE_ID = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, - 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - + private static final IntField SOURCE_ADDRESS = IntField.range(32, 55); + private static final IntField SOURCE_SUID_WACN = IntField.range(56, 75); + private static final IntField SOURCE_SUID_SYSTEM = IntField.range(76, 87); + private static final IntField SOURCE_SUID_ID = IntField.range(88, 111); private List mIdentifiers; - private VoiceServiceOptions mServiceOptions; - private Identifier mGroupAddress; - private Identifier mSourceAddress; - private Identifier mSourceSuid; + private APCO25FullyQualifiedRadioIdentifier mSource; /** * Constructs the message @@ -73,64 +57,27 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getOpcode()); - sb.append(" FM:").append(getSourceAddress()); + sb.append(" FM:").append(getSource()); sb.append(" TO:").append(getGroupAddress()); - sb.append(" SUID:").append(getSourceSuid()); sb.append(" ").append(getServiceOptions()); return sb.toString(); } /** - * Voice channel service options + * From Radio Source */ - public VoiceServiceOptions getServiceOptions() + public APCO25FullyQualifiedRadioIdentifier getSource() { - if(mServiceOptions == null) + if(mSource == null) { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - - /** - * To Talkgroup - */ - public Identifier getGroupAddress() - { - if(mGroupAddress == null) - { - mGroupAddress = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS, getOffset())); - } - - return mGroupAddress; - } - - /** - * From Radio Unit - */ - public Identifier getSourceAddress() - { - if(mSourceAddress == null) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceAddress; - } - - public Identifier getSourceSuid() - { - if(mSourceSuid == null) - { - int wacn = getMessage().getInt(FULLY_QUALIFIED_SOURCE_WACN, getOffset()); - int system = getMessage().getInt(FULLY_QUALIFIED_SOURCE_SYSTEM, getOffset()); - int id = getMessage().getInt(FULLY_QUALIFIED_SOURCE_ID, getOffset()); - - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(wacn, system, id); + int address = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSource = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); } - return mSourceSuid; + return mSource; } @Override @@ -140,8 +87,7 @@ public List getIdentifiers() { mIdentifiers = new ArrayList<>(); mIdentifiers.add(getGroupAddress()); - mIdentifiers.add(getSourceAddress()); - mIdentifiers.add(getSourceSuid()); + mIdentifiers.add(getSource()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceServiceRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceServiceRequest.java index d90d093c3..fa62a724c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceServiceRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupVoiceServiceRequest.java @@ -1,51 +1,39 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** - * Group voice service request - abbreviated format + * Group voice service request */ -public class GroupVoiceServiceRequest extends MacStructure +public class GroupVoiceServiceRequest extends MacStructureGroupVoiceService { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] GROUP_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_5_BIT_32); private List mIdentifiers; - private Identifier mGroupAddress; private Identifier mSourceAddress; - private VoiceServiceOptions mServiceOptions; /** * Constructs the message @@ -71,32 +59,6 @@ public String toString() return sb.toString(); } - /** - * Voice channel service options - */ - public VoiceServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - - /** - * To Talkgroup - */ - public Identifier getGroupAddress() - { - if(mGroupAddress == null) - { - mGroupAddress = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS, getOffset())); - } - - return mGroupAddress; - } - /** * From Radio Unit */ @@ -104,7 +66,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/IndirectGroupPagingWithoutPriority.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/IndirectGroupPagingWithoutPriority.java new file mode 100644 index 000000000..35f37cc20 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/IndirectGroupPagingWithoutPriority.java @@ -0,0 +1,205 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import java.util.ArrayList; +import java.util.List; + +/** + * Indirect group paging without priority + */ +public class IndirectGroupPagingWithoutPriority extends MacStructureVariableLength +{ + private static final IntField ID_COUNT = IntField.range(14, 15); + private static final IntField GROUP_ADDRESS_1 = IntField.length24(OCTET_3_BIT_16); + private static final IntField GROUP_ADDRESS_2 = IntField.length24(OCTET_6_BIT_40); + private static final IntField GROUP_ADDRESS_3 = IntField.length24(OCTET_9_BIT_64); + private static final IntField GROUP_ADDRESS_4 = IntField.length24(OCTET_12_BIT_88); + + private List mIdentifiers; + private Identifier mTargetGroup1; + private Identifier mTargetGroup2; + private Identifier mTargetGroup3; + private Identifier mTargetGroup4; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public IndirectGroupPagingWithoutPriority(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Length of this type of message in octets. + * @param message containing bits + * @param offset to the start of this message. + * @return length in octets. + */ + public static int getLength(CorrectedBinaryMessage message, int offset) + { + int count = message.getInt(ID_COUNT, offset); + + switch(count) + { + case 1: + return 4; + case 2: + return 6; + case 3: + return 8; + case 4: + default: + return 10; + } + } + + @Override + public int getLength() + { + return getLength(getMessage(), getOffset()); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" GROUP1:").append(getTargetGroup1()); + int count = getCount(); + + if(count > 1) + { + sb.append(" GROUP2:").append(getTargetGroup2()); + + if(count > 2) + { + sb.append(" GROUP3:").append(getTargetGroup3()); + + if(count > 3) + { + sb.append(" GROUP4:").append(getTargetGroup4()); + } + } + } + + return sb.toString(); + } + + /** + * Number of paging target addresses contained in this message + * @return addresses count (1 - 4) + */ + public int getCount() + { + return getInt(ID_COUNT); + } + + /** + * To Talkgroup 1 + */ + public Identifier getTargetGroup1() + { + if(mTargetGroup1 == null) + { + mTargetGroup1 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_1)); + } + + return mTargetGroup1; + } + + /** + * To Talkgroup 2 + */ + public Identifier getTargetGroup2() + { + if(mTargetGroup2 == null && getCount() >= 2) + { + mTargetGroup2 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_2)); + } + + return mTargetGroup2; + } + + /** + * To Talkgroup 3 + */ + public Identifier getTargetGroup3() + { + if(mTargetGroup3 == null && getCount() >= 3) + { + mTargetGroup3 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_3)); + } + + return mTargetGroup3; + } + + /** + * To Talkgroup 4 + */ + public Identifier getTargetGroup4() + { + if(mTargetGroup4 == null && getCount() >= 4) + { + mTargetGroup4 = APCO25Talkgroup.create(getInt(GROUP_ADDRESS_4)); + } + + return mTargetGroup4; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + + int count = getCount(); + + mIdentifiers.add(getTargetGroup1()); + + if(count > 1) + { + mIdentifiers.add(getTargetGroup2()); + + if(count > 2) + { + mIdentifiers.add(getTargetGroup3()); + + if(count > 3) + { + mIdentifiers.add(getTargetGroup4()); + } + } + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/IndividualPagingMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/IndividualPagingWithPriority.java similarity index 70% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/IndividualPagingMessage.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/IndividualPagingWithPriority.java index 4cea2b75e..3e7290093 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/IndividualPagingMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/IndividualPagingWithPriority.java @@ -1,54 +1,46 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; /** - * Individual paging message with priority + * Individual paging with priority */ -public class IndividualPagingMessage extends MacStructure +public class IndividualPagingWithPriority extends MacStructureVariableLength { private static final int PRIORITY_ID_1 = 8; private static final int PRIORITY_ID_2 = 9; private static final int PRIORITY_ID_3 = 10; private static final int PRIORITY_ID_4 = 11; - private static final int[] ID_COUNT = {14, 15}; - private static final int[] TARGET_ADDRESS_1 = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39}; - private static final int[] TARGET_ADDRESS_2 = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63}; - private static final int[] TARGET_ADDRESS_3 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87}; - private static final int[] TARGET_ADDRESS_4 = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, - 104, 105, 106, 107, 108, 109, 110, 111}; + private static final IntField ID_COUNT = IntField.range(14, 15); + private static final IntField TARGET_ADDRESS_1 = IntField.length24(OCTET_3_BIT_16); + private static final IntField TARGET_ADDRESS_2 = IntField.length24(OCTET_6_BIT_40); + private static final IntField TARGET_ADDRESS_3 = IntField.length24(OCTET_9_BIT_64); + private static final IntField TARGET_ADDRESS_4 = IntField.length24(OCTET_12_BIT_88); private List mIdentifiers; private Identifier mTargetAddress1; @@ -62,11 +54,41 @@ public class IndividualPagingMessage extends MacStructure * @param message containing the message bits * @param offset into the message for this structure */ - public IndividualPagingMessage(CorrectedBinaryMessage message, int offset) + public IndividualPagingWithPriority(CorrectedBinaryMessage message, int offset) { super(message, offset); } + /** + * Length of this type of message in octets. + * @param message containing bits + * @param offset to the start of this message. + * @return length in octets. + */ + public static int getLength(CorrectedBinaryMessage message, int offset) + { + int count = message.getInt(ID_COUNT, offset); + + switch(count) + { + case 1: + return 5; + case 2: + return 8; + case 3: + return 11; + case 4: + default: + return 14; + } + } + + @Override + public int getLength() + { + return getLength(getMessage(), getOffset()); + } + /** * Textual representation of this message */ @@ -197,7 +219,7 @@ public Identifier getTargetAddress1() { if(mTargetAddress1 == null) { - mTargetAddress1 = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS_1, getOffset())); + mTargetAddress1 = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS_1)); } return mTargetAddress1; @@ -210,7 +232,7 @@ public Identifier getTargetAddress2() { if(mTargetAddress2 == null && getCount() >= 2) { - mTargetAddress2 = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS_2, getOffset())); + mTargetAddress2 = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS_2)); } return mTargetAddress2; @@ -223,7 +245,7 @@ public Identifier getTargetAddress3() { if(mTargetAddress3 == null && getCount() >= 3) { - mTargetAddress3 = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS_3, getOffset())); + mTargetAddress3 = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS_3)); } return mTargetAddress3; @@ -236,7 +258,7 @@ public Identifier getTargetAddress4() { if(mTargetAddress4 == null && getCount() >= 4) { - mTargetAddress4 = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS_4, getOffset())); + mTargetAddress4 = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS_4)); } return mTargetAddress4; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/LocationRegistrationResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/LocationRegistrationResponse.java new file mode 100644 index 000000000..dfa200ba0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/LocationRegistrationResponse.java @@ -0,0 +1,131 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.reference.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * Location registration response + */ +public class LocationRegistrationResponse extends MacStructure +{ + private static final IntField RESPONSE = IntField.range(22, 23); + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_4_BIT_24); + private static final IntField RFSS = IntField.length8(OCTET_6_BIT_40); + private static final IntField SITE = IntField.length8(OCTET_7_BIT_48); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_8_BIT_56); + private Identifier mTargetAddress; + private Identifier mGroupAddress; + private Identifier mRFSS; + private Identifier mSite; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public LocationRegistrationResponse(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" LOCATION REGISTRATION ").append(getResponse()); + sb.append(" FOR GROUP:").append(getGroupAddress()); + sb.append(" RFSS:").append(getRFSS()); + sb.append(" SITE:").append(getSite()); + return sb.toString(); + } + + public Response getResponse() + { + return Response.fromValue(getInt(RESPONSE)); + } + + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25Talkgroup.createAny(getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } + + public Identifier getRFSS() + { + if(mRFSS == null) + { + mRFSS = APCO25Rfss.create(getInt(RFSS)); + } + + return mRFSS; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getInt(SITE)); + } + + return mSite; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacRelease.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacRelease.java index 4bb6f5cdd..f6670cddd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacRelease.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacRelease.java @@ -1,33 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Nac; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; @@ -38,9 +34,8 @@ public class MacRelease extends MacStructure { private static final int UNFORCED_FORCED_FLAG = 8; private static final int CALL_AUDIO_FLAG = 9; - private static final int[] TARGET_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39}; - private static final int[] COLOR_CODE = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_16); + private static final IntField COLOR_CODE = IntField.length12(OCTET_6_BIT_40 + 4); private List mIdentifiers; private Identifier mTargetAddress; @@ -96,7 +91,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -106,7 +101,7 @@ public Identifier getNac() { if(mNac == null) { - mNac = APCO25Nac.create(getMessage().getInt(COLOR_CODE, getOffset())); + mNac = APCO25Nac.create(getInt(COLOR_CODE)); } return mNac; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructure.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructure.java new file mode 100644 index 000000000..9961d1e83 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructure.java @@ -0,0 +1,120 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.message.AbstractMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; +import java.util.List; + +/** + * Structure parsing parent class for MAC message payload structures. + */ +public abstract class MacStructure extends AbstractMessage +{ + protected static final int OCTET_1_BIT_0 = 0; + protected static final int OCTET_2_BIT_8 = 8; + protected static final int OCTET_3_BIT_16 = 16; + protected static final int OCTET_4_BIT_24 = 24; + protected static final int OCTET_5_BIT_32 = 32; + protected static final int OCTET_6_BIT_40 = 40; + protected static final int OCTET_7_BIT_48 = 48; + protected static final int OCTET_8_BIT_56 = 56; + protected static final int OCTET_9_BIT_64 = 64; + protected static final int OCTET_10_BIT_72 = 72; + protected static final int OCTET_11_BIT_80 = 80; + protected static final int OCTET_12_BIT_88 = 88; + protected static final int OCTET_13_BIT_96 = 96; + protected static final int OCTET_14_BIT_104 = 104; + protected static final int OCTET_15_BIT_112 = 112; + protected static final int OCTET_16_BIT_120 = 120; + protected static final int OCTET_17_BIT_128 = 128; + protected static final int OCTET_18_BIT_136 = 136; + protected static final int OCTET_19_BIT_144 = 144; + protected static final int OCTET_20_BIT_152 = 152; + private static IntField OPCODE = IntField.length8(OCTET_1_BIT_0); + private static IntField LCCH_NAC = IntField.length12(OCTET_20_BIT_152); + + /** + * Constructs a MAC structure parser + * + * @param message containing a MAC structure + * @param offset in the message to the start of the structure + */ + protected MacStructure(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * List of identifiers provided by this structure + */ + public abstract List getIdentifiers(); + + /** + * Opcode for the message argument + * @param message containing a mac opcode message + * @param offset into the message + * @return opcode + */ + public static MacOpcode getOpcode(CorrectedBinaryMessage message, int offset) + { + return MacOpcode.fromValue(message.getInt(OPCODE, offset)); + } + + /** + * Numeric value of the opcode + * @param message containing a mac opcode message + * @param offset into the message to the start of the mac sequence + * @return integer value + */ + public static int getOpcodeNumber(CorrectedBinaryMessage message, int offset) + { + return message.getInt(OPCODE, offset); + } + + /** + * Opcode for this message + */ + public MacOpcode getOpcode() + { + return getOpcode(getMessage(), getOffset()); + } + + /** + * Opcode numeric value for this structure + */ + public int getOpcodeNumber() + { + return getOpcodeNumber(getMessage(), getOffset()); + } + + /** + * Parses the NAC code from a LCCH MAC PDU content payload. + * @param message containing a LCCH MAC PDU content payload. + * @return nac value. + */ + public static int getLcchNac(CorrectedBinaryMessage message) + { + return message.getInt(LCCH_NAC); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureDataService.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureDataService.java new file mode 100644 index 000000000..e7fcd157d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureDataService.java @@ -0,0 +1,57 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; + +/** + * Data service mac structure base class. + */ +public abstract class MacStructureDataService extends MacStructure +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(8); + private DataServiceOptions mServiceOptions; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MacStructureDataService(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Data channel service options + */ + public DataServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new DataServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureFailedPDUCRC.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureFailedPDUCRC.java new file mode 100644 index 000000000..586921774 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureFailedPDUCRC.java @@ -0,0 +1,54 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import java.util.List; + +/** + * MAC structure to indicate that the MAC PDU CRC-12 or CRC-16 check failed and the contents of the MAC PDU are invalid + * or untrustworthy. + */ +public class MacStructureFailedPDUCRC extends MacStructure +{ + /** + * Constructs a MAC structure parser + * + * @param message containing a MAC structure + * @param offset in the message to the start of the structure + */ + public MacStructureFailedPDUCRC(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + @Override + public String toString() + { + return "[CRC-FAILED] MAC MESSAGE PDU CONTENT FAILED CRC CHECK"; + } + + @Override + public List getIdentifiers() + { + return null; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureFailedRS.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureFailedRS.java new file mode 100644 index 000000000..a8954df2a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureFailedRS.java @@ -0,0 +1,53 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import java.util.List; + +/** + * MAC structure to indicate that the Reed Solomon (63, 35, 29) decode failed. + */ +public class MacStructureFailedRS extends MacStructure +{ + /** + * Constructs a MAC structure parser + * + * @param message containing a MAC structure + * @param offset in the message to the start of the structure + */ + public MacStructureFailedRS(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + @Override + public String toString() + { + return "[CRC-FAILED] REED SOLOMON 63,35,29 DECODE FAILED"; + } + + @Override + public List getIdentifiers() + { + return null; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureGroupVoiceService.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureGroupVoiceService.java new file mode 100644 index 000000000..7ade49c9d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureGroupVoiceService.java @@ -0,0 +1,58 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; + +/** + * Group voice service Mac Structure - opcodes for group voice with service options + */ +public abstract class MacStructureGroupVoiceService extends MacStructureVoiceService +{ + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_3_BIT_16); + private Identifier mGroupAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MacStructureGroupVoiceService(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * To Talkgroup + */ + public Identifier getGroupAddress() + { + if(mGroupAddress == null) + { + mGroupAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); + } + + return mGroupAddress; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureMultiFragment.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureMultiFragment.java new file mode 100644 index 000000000..cd6a23aea --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureMultiFragment.java @@ -0,0 +1,121 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import java.util.ArrayList; +import java.util.List; + +/** + * Base class for a multi-fragment message. The multi-fragment message has a data length field that indicates the total + * byte length of this message and any MultiFragmentContinuationMessages. + */ +public abstract class MacStructureMultiFragment extends MacStructureVariableLength +{ + private static final IntField DATA_LENGTH = IntField.length8(OCTET_3_BIT_16); + private List mContinuationMessages = new ArrayList<>(); + + /** + * Constructs a MAC structure parser + * + * @param message containing a MAC structure + * @param offset in the message to the start of the structure + */ + protected MacStructureMultiFragment(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Total length of the multi-fragment message that spans multiple mac structures. Does not include the first two + * octets of each fragment that carries the OPCODE and LENGTH fields in the overall count. + * @param message containing one or more mac structures + * @param offset to the start of the mac structure + * @return data length for the multi-fragment message + */ + public static int getDataLength(CorrectedBinaryMessage message, int offset) + { + return message.getInt(DATA_LENGTH, offset); + } + + /** + * Total length of this multi-fragment message + * @return data length for this multi-fragment message + */ + public int getDataLength() + { + return getInt(DATA_LENGTH); + } + + /** + * Adds the continuation message to this base message. + * @param continuationMessage to add. + */ + public void addContinuationMessage(MultiFragmentContinuationMessage continuationMessage) + { + mContinuationMessages.add(continuationMessage); + } + + /** + * Access the continuation message + * @param index of the continuation message + * @return continuation message if it exists, or null. + */ + protected MultiFragmentContinuationMessage getFragment(int index) + { + if(hasFragment(index)) + { + return mContinuationMessages.get(index); + } + + return null; + } + + /** + * Indicates if this multi-fragment message is completely assembled with continuation messages such that the + * combined octet length of all messages (minus first 2 octets each) is less than or equal to the data length + * specified in the base message. + * @return true if this message has all of its continuation fragments. + */ + public boolean isComplete() + { + int dataLength = getDataLength(); + + //Data length does not include first 2 octest of each message - subtract 2 from the length of each message. + int currentLength = getLength() - 2; + for(MultiFragmentContinuationMessage continuationMessage: mContinuationMessages) + { + currentLength += (continuationMessage.getLength() - 2); + } + + return dataLength <= currentLength; + } + + /** + * Indicates if this message contains the fragment continuation message referenced by the index argument. + * @param index to test for existence. + * @return true if it exists. + */ + protected boolean hasFragment(int index) + { + return index < mContinuationMessages.size(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureUnitVoiceService.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureUnitVoiceService.java new file mode 100644 index 000000000..36bf6df99 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureUnitVoiceService.java @@ -0,0 +1,58 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; + +/** + * Unit voice service mac structure - opcodes that address (target) a radio + */ +public abstract class MacStructureUnitVoiceService extends MacStructureVoiceService +{ + private static final IntField TARGET_ADDRESS = IntField.range(16, 39); + private Identifier mTargetAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MacStructureUnitVoiceService(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * To Radio Unit + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureVariableLength.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureVariableLength.java new file mode 100644 index 000000000..e15c65dce --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureVariableLength.java @@ -0,0 +1,73 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; + +/** + * Base class for a variable length message. + */ +public abstract class MacStructureVariableLength extends MacStructure +{ + private static final IntField LENGTH = IntField.range(10, 15); + + /** + * Constructs a MAC structure parser + * + * @param message containing a MAC structure + * @param offset in the message to the start of the structure + */ + protected MacStructureVariableLength(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Utility method to discover the length of this mac structure + * @param message containing one or more mac structures + * @param offset to the start of the mac structure + * @return data length for the multi-fragment message + */ + public static int getLength(CorrectedBinaryMessage message, int offset) + { + MacOpcode opcode = MacStructure.getOpcode(message, offset); + + switch(opcode) + { + case TDMA_11_INDIRECT_GROUP_PAGING_WITHOUT_PRIORITY: + return IndirectGroupPagingWithoutPriority.getLength(message, offset); + case TDMA_12_INDIVIDUAL_PAGING_WITH_PRIORITY: + return IndividualPagingWithPriority.getLength(message, offset); + default: + return message.getInt(LENGTH, offset); + } + } + + /** + * Length of this mac structure. + * @return data length for this multi-fragment message + */ + public int getLength() + { + return getLength(getMessage(), getOffset()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureVendor.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureVendor.java new file mode 100644 index 000000000..8c1bc3267 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureVendor.java @@ -0,0 +1,112 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; +import io.github.dsheirer.module.decode.p25.reference.Vendor; + +/** + * Base class for custom vendor implementations + */ +public abstract class MacStructureVendor extends MacStructureVariableLength +{ + private static final IntField VENDOR = IntField.length8(OCTET_2_BIT_8); + private static final IntField LENGTH = IntField.range(18, 23); + + /** + * Constructs a MAC structure parser + * + * @param message containing a MAC structure + * @param offset in the message to the start of the structure + */ + protected MacStructureVendor(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Overrides the primary method to ensure we get the correct vendor mac opcode representation + */ + @Override + public MacOpcode getOpcode() + { + return MacOpcode.fromValue(getOpcodeNumber(), getVendor()); + } + + /** + * Utility method to lookup vendor specified for this message + * @param message bits + * @param offset to the start of the structure + * @return vendor or UNKNOWN. + */ + public static Vendor getVendor(CorrectedBinaryMessage message, int offset) + { + return Vendor.fromValue(message.getInt(VENDOR, offset)); + } + + /** + * Vendor specified for this message + * @return vendor or UNKNOWN. + */ + public Vendor getVendor() + { + return getVendor(getMessage(), getOffset()); + } + + /** + * ID number for the vendor. + */ + public int getVendorID() + { + return getInt(VENDOR); + } + + /** + * Utility method to discover the length of this mac structure + * @param message containing one or more mac structures + * @param offset to the start of the mac structure + * @return data length for the multi-fragment message + */ + public static int getLength(CorrectedBinaryMessage message, int offset) + { + MacOpcode opcode = MacStructure.getOpcode(message, offset); + + switch(opcode) + { + case TDMA_11_INDIRECT_GROUP_PAGING_WITHOUT_PRIORITY: + return IndirectGroupPagingWithoutPriority.getLength(message, offset); + case TDMA_12_INDIVIDUAL_PAGING_WITH_PRIORITY: + return IndividualPagingWithPriority.getLength(message, offset); + default: + return message.getInt(LENGTH, offset); + } + } + + /** + * Length of this mac structure. + * @return data length for this multi-fragment message + */ + public int getLength() + { + return getLength(getMessage(), getOffset()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureVoiceService.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureVoiceService.java new file mode 100644 index 000000000..4148d0bf9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MacStructureVoiceService.java @@ -0,0 +1,58 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; + +/** + * Voice service mac structure - for opcodes that start with Voice Service Options. + */ +public abstract class MacStructureVoiceService extends MacStructure implements IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_2_BIT_8); + private VoiceServiceOptions mServiceOptions; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MacStructureVoiceService(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Voice service options + */ + public VoiceServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdate.java new file mode 100644 index 000000000..c8b14725b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdate.java @@ -0,0 +1,75 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; + +/** + * Message update base implementation + */ +public abstract class MessageUpdate extends MacStructure +{ + private static final IntField MESSAGE = IntField.length16(OCTET_3_BIT_16); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_5_BIT_32); + + private Identifier mShortDataMessage; + private Identifier mTargetAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MessageUpdate(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Short data message + */ + public Identifier getShortDataMessage() + { + if(mShortDataMessage == null) + { + mShortDataMessage = APCO25ShortDataMessage.create(getInt(MESSAGE)); + } + + return mShortDataMessage; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateAbbreviated.java index 077805aba..f2095984c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateAbbreviated.java @@ -1,50 +1,39 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; /** - * Message update - abbreviated format + * Message update abbreviated */ -public class MessageUpdateAbbreviated extends MacStructure +public class MessageUpdateAbbreviated extends MessageUpdate { - private static final int[] MESSAGE = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, - 73, 74, 75, 76, 77, 78, 79}; + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_8_BIT_56); private List mIdentifiers; - private Identifier mShortDataMessage; - private Identifier mTargetAddress; private Identifier mSourceAddress; /** @@ -71,33 +60,6 @@ public String toString() return sb.toString(); } - /** - * Short data message - */ - public Identifier getShortDataMessage() - { - if(mShortDataMessage == null) - { - mShortDataMessage = APCO25ShortDataMessage.create(getMessage().getInt(MESSAGE)); - } - - return mShortDataMessage; - } - - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - /** * From Radio Unit */ @@ -105,7 +67,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateExtended.java deleted file mode 100644 index 5bdea5832..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateExtended.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; -import java.util.List; - -/** - * Message update - abbreviated format - */ -public class MessageUpdateExtended extends MacStructure -{ - private static final int[] MESSAGE = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - private static final int[] SOURCE_WACN = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, - 73, 74, 75}; - private static final int[] SOURCE_SYSTEM = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] SOURCE_ADDRESS = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, - 104, 105, 106, 107, 108, 109, 110, 111}; - - private List mIdentifiers; - private Identifier mShortDataMessage; - private Identifier mTargetAddress; - private Identifier mSourceSuid; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public MessageUpdateExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceSuid()); - sb.append(" TO:").append(getTargetAddress()); - sb.append(" MSG:").append(getShortDataMessage()); - return sb.toString(); - } - - /** - * Short data message - */ - public Identifier getShortDataMessage() - { - if(mShortDataMessage == null) - { - mShortDataMessage = APCO25ShortDataMessage.create(getMessage().getInt(MESSAGE)); - } - - return mShortDataMessage; - } - - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - /** - * From Radio Unit - */ - public Identifier getSourceSuid() - { - if(mSourceSuid == null) - { - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(getMessage().getInt(SOURCE_WACN, getOffset()), - getMessage().getInt(SOURCE_SYSTEM, getOffset()), getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceSuid; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceSuid()); - mIdentifiers.add(getShortDataMessage()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateExtendedLCCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateExtendedLCCH.java new file mode 100644 index 000000000..240807fcc --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateExtendedLCCH.java @@ -0,0 +1,138 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.message.APCO25ShortDataMessage; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Message update extended LCCH + */ +public class MessageUpdateExtendedLCCH extends MacStructureMultiFragment +{ + private static final IntField MESSAGE = IntField.length16(OCTET_4_BIT_24); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_6_BIT_40); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_9_BIT_64); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_11_BIT_80 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_13_BIT_96); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_16_BIT_120); + private static final IntField FRAGMENT_0_TARGET_SUID_WACN = IntField.length20(OCTET_3_BIT_16); + private static final IntField FRAGMENT_0_TARGET_SUID_SYSTEM = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField FRAGMENT_0_TARGET_SUID_ID = IntField.length24(OCTET_7_BIT_48); + + private Identifier mShortDataMessage; + private APCO25FullyQualifiedRadioIdentifier mTargetSUID; + private APCO25FullyQualifiedRadioIdentifier mSourceSUID; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MessageUpdateExtendedLCCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + if(getTargetSUID() != null) + { + sb.append(" TO:").append(getTargetSUID()); + } + + sb.append(" FM:").append(getSourceSUID()); + sb.append(" SDM:").append(getShortDataMessage()); + return sb.toString(); + } + + /** + * Short data message + */ + public Identifier getShortDataMessage() + { + if(mShortDataMessage == null) + { + mShortDataMessage = APCO25ShortDataMessage.create(getInt(MESSAGE)); + } + + return mShortDataMessage; + } + + + /** + * To Talkgroup + */ + public Identifier getTargetSUID() + { + if(mTargetSUID == null && hasFragment(0)) + { + int address = getInt(TARGET_ADDRESS); + int wacn = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_WACN); + int system = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_SYSTEM); + int id = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_ID); + mTargetSUID = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mTargetSUID; + } + + /** + * From Radio Unit + */ + public APCO25FullyQualifiedRadioIdentifier getSourceSUID() + { + if(mSourceSUID == null) + { + int address = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSourceSUID = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mSourceSUID; + } + + @Override + public List getIdentifiers() + { + List identifiers = new ArrayList<>(); + identifiers.add(getSourceSUID()); + + if(getTargetSUID() != null) + { + identifiers.add(getTargetSUID()); + } + + return identifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateExtendedVCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateExtendedVCH.java new file mode 100644 index 000000000..2c0b08331 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MessageUpdateExtendedVCH.java @@ -0,0 +1,94 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Message update extended VCH + */ +public class MessageUpdateExtendedVCH extends MessageUpdate +{ + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_8_BIT_56); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_10_BIT_72 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_12_BIT_88); + private List mIdentifiers; + private Identifier mSourceSUID; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MessageUpdateExtendedVCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceSUID()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" MSG:").append(getShortDataMessage()); + return sb.toString(); + } + + /** + * From Radio Unit + */ + public Identifier getSourceSUID() + { + if(mSourceSUID == null) + { + int wacn = getMessage().getInt(SOURCE_SUID_WACN, getOffset()); + int system = getMessage().getInt(SOURCE_SUID_SYSTEM, getOffset()); + int id = getMessage().getInt(SOURCE_SUID_ID, getOffset()); + //Fully qualified, but not aliased - reuse the ID as the address. + mSourceSUID = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceSUID; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceSUID()); + mIdentifiers.add(getShortDataMessage()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MultiFragmentContinuationMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MultiFragmentContinuationMessage.java new file mode 100644 index 000000000..b463453fe --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/MultiFragmentContinuationMessage.java @@ -0,0 +1,89 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import java.util.Collections; +import java.util.List; + +/** + * Multi-Fragment Continuation Message + */ +public class MultiFragmentContinuationMessage extends MacStructure +{ + private static final int[] LENGTH = {10, 11, 12, 13, 14, 15}; //Of this packet + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MultiFragmentContinuationMessage(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MULTI-FRAGMENT CONTINUATION MESSAGE LENGTH:").append(getLength()); + return sb.toString(); + } + + /** + * Access the message field integer value from this continuation message. + * @param indexes for the field. + * @return integer value. + */ + public int getInt(int[] indexes) + { + return getMessage().getInt(indexes, getOffset()); + } + + /** + * Access the boolean field value. + * @param index of the value in this message. Note: this will be adjusted to the offset of this message. + * @return boolean value. + */ + public boolean get(int index) + { + return getMessage().get(index + getOffset()); + } + + /** + * Length of this packet. + * @return length in bytes. + */ + public int getLength() + { + return getMessage().getInt(LENGTH, getOffset()); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastExplicit.java similarity index 54% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastExtended.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastExplicit.java index fc7106673..493014571 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastExplicit.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; @@ -33,27 +31,25 @@ import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.Collections; import java.util.List; /** - * Network status broadcast - extended format + * Network status broadcast explicit */ -public class NetworkStatusBroadcastExtended extends MacStructure implements IFrequencyBandReceiver +public class NetworkStatusBroadcastExplicit extends MacStructure implements IFrequencyBandReceiver { - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] WACN = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; - private static final int[] SYSTEM_ID = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] TRANSMIT_FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] RECEIVE_FREQUENCY_BAND = {64, 65, 66, 67}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; - private static final int[] SERVICE_CLASS = {80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] COLOR_CODE = {92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; + private static final IntField LRA = IntField.length8(OCTET_2_BIT_8); + private static final IntField WACN = IntField.length20(OCTET_3_BIT_16); + private static final IntField SYSTEM_ID = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_48); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_9_BIT_64); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_9_BIT_64 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_11_BIT_80); + private static final IntField COLOR_CODE = IntField.length12(OCTET_12_BIT_88 + 4); private List mIdentifiers; private Identifier mLRA; @@ -69,7 +65,7 @@ public class NetworkStatusBroadcastExtended extends MacStructure implements IFre * @param message containing the message bits * @param offset into the message for this structure */ - public NetworkStatusBroadcastExtended(CorrectedBinaryMessage message, int offset) + public NetworkStatusBroadcastExplicit(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -95,15 +91,14 @@ public String toString() */ public ScrambleParameters getScrambleParameters() { - return new ScrambleParameters(getMessage().getInt(WACN, getOffset()), getMessage().getInt(SYSTEM_ID, getOffset()), - getMessage().getInt(COLOR_CODE, getOffset())); + return new ScrambleParameters(getInt(WACN), getInt(SYSTEM_ID), getInt(COLOR_CODE)); } public Identifier getLRA() { if(mLRA == null) { - mLRA = APCO25Lra.create(getMessage().getInt(LRA, getOffset())); + mLRA = APCO25Lra.create(getInt(LRA)); } return mLRA; @@ -113,7 +108,7 @@ public Identifier getWACN() { if(mWACN == null) { - mWACN = APCO25Wacn.create(getMessage().getInt(WACN, getOffset())); + mWACN = APCO25Wacn.create(getInt(WACN)); } return mWACN; @@ -123,7 +118,7 @@ public Identifier getSystem() { if(mSystem == null) { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID, getOffset())); + mSystem = APCO25System.create(getInt(SYSTEM_ID)); } return mSystem; @@ -133,10 +128,10 @@ public APCO25Channel getChannel() { if(mChannel == null) { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset())); + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), + getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), + getInt(RECEIVE_CHANNEL_NUMBER)); } return mChannel; @@ -146,7 +141,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = SystemServiceClass.create(getMessage().getInt(SERVICE_CLASS, getOffset())); + mSystemServiceClass = SystemServiceClass.create(getInt(SERVICE_CLASS)); } return mSystemServiceClass; @@ -156,7 +151,7 @@ public Identifier getNAC() { if(mNAC == null) { - mNAC = APCO25Nac.create(getMessage().getInt(COLOR_CODE, getOffset())); + mNAC = APCO25Nac.create(getInt(COLOR_CODE)); } return mNAC; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastImplicit.java similarity index 58% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastAbbreviated.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastImplicit.java index 6952e951f..ad1a8b2a7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NetworkStatusBroadcastImplicit.java @@ -1,28 +1,26 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; @@ -33,25 +31,23 @@ import io.github.dsheirer.module.decode.p25.identifier.channel.P25Channel; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; import java.util.Collections; import java.util.List; /** - * Network status broadcast - abbreviated format + * Network status broadcast implicit */ -public class NetworkStatusBroadcastAbbreviated extends MacStructure implements IFrequencyBandReceiver +public class NetworkStatusBroadcastImplicit extends MacStructure implements IFrequencyBandReceiver { - private static final int[] LRA = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] WACN = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; - private static final int[] SYSTEM_ID = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] FREQUENCY_BAND = {48, 49, 50, 51}; - private static final int[] CHANNEL_NUMBER = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SERVICE_CLASS = {64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] COLOR_CODE = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; + private static final IntField LRA = IntField.length8(OCTET_2_BIT_8); + private static final IntField WACN = IntField.length20(OCTET_3_BIT_16); + private static final IntField SYSTEM_ID = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_48); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_9_BIT_64); + private static final IntField COLOR_CODE = IntField.length12(OCTET_10_BIT_72 + 4); private List mIdentifiers; private Identifier mLRA; @@ -67,7 +63,7 @@ public class NetworkStatusBroadcastAbbreviated extends MacStructure implements I * @param message containing the message bits * @param offset into the message for this structure */ - public NetworkStatusBroadcastAbbreviated(CorrectedBinaryMessage message, int offset) + public NetworkStatusBroadcastImplicit(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -93,15 +89,14 @@ public String toString() */ public ScrambleParameters getScrambleParameters() { - return new ScrambleParameters(getMessage().getInt(WACN,getOffset()), getMessage().getInt(SYSTEM_ID, getOffset()), - getMessage().getInt(COLOR_CODE, getOffset())); + return new ScrambleParameters(getInt(WACN), getInt(SYSTEM_ID), getInt(COLOR_CODE)); } public Identifier getLRA() { if(mLRA == null) { - mLRA = APCO25Lra.create(getMessage().getInt(LRA, getOffset())); + mLRA = APCO25Lra.create(getInt(LRA)); } return mLRA; @@ -111,7 +106,7 @@ public Identifier getWACN() { if(mWACN == null) { - mWACN = APCO25Wacn.create(getMessage().getInt(WACN, getOffset())); + mWACN = APCO25Wacn.create(getInt(WACN)); } return mWACN; @@ -121,7 +116,7 @@ public Identifier getSystem() { if(mSystem == null) { - mSystem = APCO25System.create(getMessage().getInt(SYSTEM_ID, getOffset())); + mSystem = APCO25System.create(getInt(SYSTEM_ID)); } return mSystem; @@ -137,8 +132,8 @@ public APCO25Channel getChannel() { if(mChannel == null) { - P25Channel channel = new P25Channel(getMessage().getInt(FREQUENCY_BAND, getOffset()), - getMessage().getInt(CHANNEL_NUMBER, getOffset())); + P25Channel channel = new P25Channel(getInt(FREQUENCY_BAND), + getInt(CHANNEL_NUMBER)); mChannel = new APCO25Channel(channel); } @@ -149,7 +144,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = SystemServiceClass.create(getMessage().getInt(SERVICE_CLASS, getOffset())); + mSystemServiceClass = SystemServiceClass.create(getInt(SERVICE_CLASS)); } return mSystemServiceClass; @@ -159,7 +154,7 @@ public Identifier getNAC() { if(mNAC == null) { - mNAC = APCO25Nac.create(getMessage().getInt(COLOR_CODE, getOffset())); + mNAC = APCO25Nac.create(getInt(COLOR_CODE)); } return mNAC; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NullAvoidZeroBiasInformation.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NullAvoidZeroBiasInformation.java new file mode 100644 index 000000000..1b95cb372 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NullAvoidZeroBiasInformation.java @@ -0,0 +1,59 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import java.util.Collections; +import java.util.List; + +/** + * Null avoid zero bias information message + */ +public class NullAvoidZeroBiasInformation extends MacStructureVariableLength +{ + /** + * Constructs the message + * + * @param message containing the message bits + * @param timestamp of the final bit of the message + */ + public NullAvoidZeroBiasInformation(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode().toString()); + sb.append(" LENGTH:").append(getLength()); + sb.append(" MSG:").append(getMessage().getSubMessage(getOffset(), getOffset() + (getLength() * 8)).toHexString()); + return sb.toString(); + } + + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NullInformation.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NullInformation.java new file mode 100644 index 000000000..df1cba228 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NullInformation.java @@ -0,0 +1,57 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import java.util.Collections; +import java.util.List; + +/** + * Null (empty) information message + */ +public class NullInformation extends MacStructure +{ + /** + * Constructs the message + * + * @param message containing the message bits + * @param timestamp of the final bit of the message + */ + public NullInformation(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode().toString()); + return sb.toString(); + } + + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NullInformationMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NullInformationMessage.java deleted file mode 100644 index 94d64a03c..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/NullInformationMessage.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.Collections; -import java.util.List; - -/** - * Null (empty) information message - */ -public class NullInformationMessage extends MacStructure -{ - /** - * Constructs the message - * - * @param message containing the message bits - * @param timestamp of the final bit of the message - */ - public NullInformationMessage(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - return getOpcode().toString(); - } - - public List getIdentifiers() - { - return Collections.EMPTY_LIST; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/PowerControlSignalQuality.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/PowerControlSignalQuality.java index 434dadf1b..5d449dabc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/PowerControlSignalQuality.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/PowerControlSignalQuality.java @@ -1,35 +1,31 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase2.enumeration.BER; import io.github.dsheirer.module.decode.p25.phase2.enumeration.RFLevel; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -37,10 +33,9 @@ */ public class PowerControlSignalQuality extends MacStructure { - private static final int[] TARGET_ADDRESS = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31}; - private static final int[] RF_LEVEL = {32, 33, 34, 35}; - private static final int[] BIT_ERROR_RATE = {36, 37, 38, 39}; + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_2_BIT_8); + private static final IntField RF_LEVEL = IntField.length4(OCTET_5_BIT_32); + private static final IntField BIT_ERROR_RATE = IntField.length4(OCTET_5_BIT_32 + 4); private List mIdentifiers; private Identifier mTargetAddress; @@ -76,7 +71,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -84,12 +79,12 @@ public Identifier getTargetAddress() public RFLevel getRFLevel() { - return RFLevel.fromValue(getMessage().getInt(RF_LEVEL, getOffset())); + return RFLevel.fromValue(getInt(RF_LEVEL)); } public BER getBitErrorRate() { - return BER.fromValue(getMessage().getInt(BIT_ERROR_RATE, getOffset())); + return BER.fromValue(getInt(BIT_ERROR_RATE)); } @Override @@ -97,8 +92,7 @@ public List getIdentifiers() { if(mIdentifiers == null) { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); + mIdentifiers = Collections.singletonList(getTargetAddress()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/PushToTalk.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/PushToTalk.java index 7fbdba43c..c857ae8a0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/PushToTalk.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/PushToTalk.java @@ -1,29 +1,27 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.audio.codec.mbe.IEncryptionSyncParameters; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier; import io.github.dsheirer.module.decode.p25.audio.Phase2EncryptionSyncParameters; @@ -31,25 +29,20 @@ import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; /** - * Call start. - * - * Similar to a P25 Phase 1 Header Data Unit (HDU). + * Push-To-Talk, Call start. */ public class PushToTalk extends MacStructure { private static int MESSAGE_INDICATOR_START = 8; private static int MESSAGE_INDICATOR_END = 79; - private static int[] ALGORITHM_ID = {80, 81, 82, 83, 84, 85, 86, 87}; - private static int[] KEY_ID = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; - private static int[] SOURCE_ADDRESS = {104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, - 119, 120, 121, 122, 123, 124, 125, 126, 127}; - private static int[] GROUP_ADDRESS = {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143}; + private static final IntField ALGORITHM_ID = IntField.length8(OCTET_11_BIT_80); + private static final IntField KEY_ID = IntField.length16(OCTET_12_BIT_88); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_14_BIT_104); + private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_17_BIT_128); private EncryptionKeyIdentifier mEncryptionKey; private Identifier mSourceAddress; @@ -78,8 +71,12 @@ public MacOpcode getOpcode() public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("FM:").append(getSourceAddress()); - sb.append(" TO:").append(getGroupAddress()); + if(hasSourceAddress()) + { + sb.append("FM:").append(getSourceAddress()).append(" "); + } + + sb.append("TO:").append(getGroupAddress()); if(isEncrypted()) { @@ -123,13 +120,17 @@ public EncryptionKeyIdentifier getEncryptionKey() { if(mEncryptionKey == null) { - mEncryptionKey = EncryptionKeyIdentifier.create(APCO25EncryptionKey.create(getMessage().getInt(ALGORITHM_ID, getOffset()), - getMessage().getInt(KEY_ID, getOffset()))); + mEncryptionKey = EncryptionKeyIdentifier.create(APCO25EncryptionKey.create(getInt(ALGORITHM_ID), getInt(KEY_ID))); } return mEncryptionKey; } + public boolean hasSourceAddress() + { + return getInt(SOURCE_ADDRESS) > 0; + } + /** * Calling (source) radio identifier */ @@ -137,7 +138,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; @@ -150,7 +151,7 @@ public Identifier getGroupAddress() { if(mGroupAddress == null) { - mGroupAddress = APCO25Talkgroup.create(getMessage().getInt(GROUP_ADDRESS, getOffset())); + mGroupAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS)); } return mGroupAddress; @@ -165,7 +166,10 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getSourceAddress()); + if(hasSourceAddress()) + { + mIdentifiers.add(getSourceAddress()); + } mIdentifiers.add(getGroupAddress()); mIdentifiers.add(getEncryptionKey()); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/QueuedResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/QueuedResponse.java index f6c7a7466..38aa23fc5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/QueuedResponse.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/QueuedResponse.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -20,12 +20,11 @@ package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.reference.QueuedResponseReason; - import java.util.ArrayList; import java.util.List; @@ -35,12 +34,10 @@ public class QueuedResponse extends MacStructure { private static final int ADDITIONAL_INFORMATION_INDICATOR = 8; - private static final int[] SERVICE_TYPE = {10, 11, 12, 13, 14, 15}; - private static final int[] REASON = {24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] ADDITIONAL_INFO = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - private static final int[] TARGET_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, - 74, 75, 76, 77, 78, 79}; + private static final IntField SERVICE_TYPE = IntField.range(10, 15); + private static final IntField REASON = IntField.length8(OCTET_3_BIT_16); + private static final IntField ADDITIONAL_INFO = IntField.length24(OCTET_4_BIT_24); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_48); private QueuedResponseReason mQueuedResponseReason; private String mAdditionalInfo; @@ -86,8 +83,7 @@ public String getAdditionalInfo() { if(mAdditionalInfo == null) { - int arguments = getMessage().getInt(ADDITIONAL_INFO, getOffset()); - mAdditionalInfo = Integer.toHexString(arguments).toUpperCase(); + mAdditionalInfo = Integer.toHexString(getInt(ADDITIONAL_INFO)).toUpperCase(); } return mAdditionalInfo; @@ -98,14 +94,14 @@ public String getAdditionalInfo() */ public MacOpcode getQueuedResponseServiceType() { - return MacOpcode.fromValue(getMessage().getInt(SERVICE_TYPE, getOffset())); + return MacOpcode.fromValue(getInt(SERVICE_TYPE)); } public QueuedResponseReason getQueuedResponseReason() { if(mQueuedResponseReason == null) { - mQueuedResponseReason = QueuedResponseReason.fromCode(getMessage().getInt(REASON, getOffset())); + mQueuedResponseReason = QueuedResponseReason.fromCode(getInt(REASON)); } return mQueuedResponseReason; @@ -115,7 +111,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommand.java index d15db087a..17c472329 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommand.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommand.java @@ -1,50 +1,41 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; import java.util.List; /** - * Radio unit monitor command + * Radio unit monitor command base implementation */ -public class RadioUnitMonitorCommand extends MacStructure +public abstract class RadioUnitMonitorCommand extends MacStructure { - private static final int[] TRANSMIT_TIME = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int SILENT_MONITOR = 24; //?? - private static final int[] TRANSMIT_MULTIPLIER = {30, 31}; - private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55}; - private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, - 74, 75, 76, 77, 78, 79}; + private static final IntField TRANSMIT_TIME = IntField.length8(OCTET_3_BIT_16); + private static final int SILENT_MONITOR = 24; + private static final IntField TRANSMIT_MULTIPLIER = IntField.range(30, 31); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_5_BIT_32); private List mIdentifiers; - private Identifier mSourceAddress; private Identifier mTargetAddress; /** @@ -58,25 +49,6 @@ public RadioUnitMonitorCommand(CorrectedBinaryMessage message, int offset) super(message, offset); } - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceAddress()); - sb.append(" TO:").append(getTargetAddress()); - - if(isSilentMonitor()) - { - sb.append(" SILENT MONITORING"); - } - sb.append(" TIME:").append(getTransmitTime()); - sb.append(" MULTIPLIER:").append(getTransmitMultiplier()); - return sb.toString(); - } - /** * Indicates if the target radio should not indicate to the user that the radio is being monitored. */ @@ -90,7 +62,7 @@ public boolean isSilentMonitor() */ public int getTransmitTime() { - return getMessage().getInt(TRANSMIT_TIME, getOffset()); + return getInt(TRANSMIT_TIME); } /** @@ -98,7 +70,7 @@ public int getTransmitTime() */ public int getTransmitMultiplier() { - return getMessage().getInt(TRANSMIT_MULTIPLIER, getOffset()); + return getInt(TRANSMIT_MULTIPLIER); } /** @@ -108,35 +80,9 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; } - - /** - * From Radio Unit - */ - public Identifier getSourceAddress() - { - if(mSourceAddress == null) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceAddress; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceAddress()); - } - - return mIdentifiers; - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandAbbreviated.java new file mode 100644 index 000000000..d0cca2030 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandAbbreviated.java @@ -0,0 +1,94 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Radio unit monitor command abbreviated + */ +public class RadioUnitMonitorCommandAbbreviated extends RadioUnitMonitorCommand +{ + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_8_BIT_56); + + private List mIdentifiers; + private Identifier mSourceAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RadioUnitMonitorCommandAbbreviated(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + + if(isSilentMonitor()) + { + sb.append(" SILENT MONITORING"); + } + sb.append(" TIME:").append(getTransmitTime()); + sb.append(" MULTIPLIER:").append(getTransmitMultiplier()); + return sb.toString(); + } + + /** + * From Radio Unit + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandEnhanced.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandEnhanced.java deleted file mode 100644 index e94c76b50..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandEnhanced.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.Encryption; - -import java.util.ArrayList; -import java.util.List; - -/** - * Radio unit monitor command - enhanced format. Commands the target radio to initiate a group or unit-to-unit call to - * either the source address to or the talkgroup, either clear or encrypted - */ -public class RadioUnitMonitorCommandEnhanced extends MacStructure -{ - private static final int[] TARGET_ADDRESS = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31}; - private static final int[] TALKGROUP_ID = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] SOURCE_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - private static final int SM = 72; //Stealth Mode? - private static final int TG = 73; - private static final int[] TRANSMIT_TIME = {80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] KEY_ID = {88, 89, 90, 91, 92, 93, 94, 95}; - private static final int[] ALGORITHM_ID = {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; - - - private List mIdentifiers; - private Identifier mTargetAddress; - private Identifier mTalkgroupId; - private Identifier mSourceAddress; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public RadioUnitMonitorCommandEnhanced(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" TO:").append(getTargetAddress()); - sb.append(" CALL RADIO:").append(getSourceAddress()); - sb.append(" OR TALKGROUP:").append(getTalkgroupId()); - - if(isStealthMode()) - { - sb.append(" STEALTH MODE"); - } - - if(isTG()) - { - sb.append(" ?TG?"); - } - - long transmitTime = getTransmitTime(); - - if(transmitTime > 0) - { - sb.append(" TRANSMIT TIME:").append(transmitTime / 1000d).append("secs"); - } - - Encryption encryption = getEncryption(); - - if(encryption != Encryption.UNENCRYPTED) - { - sb.append(" USE ENCRYPTION:").append(encryption); - sb.append(" KEY:").append(getEncryptionKeyId()); - } - - return sb.toString(); - } - - /** - * To radio address - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - /** - * Talkgroup to call - */ - public Identifier getTalkgroupId() - { - if(mTalkgroupId == null) - { - mTalkgroupId = APCO25Talkgroup.create(getMessage().getInt(TALKGROUP_ID, getOffset())); - } - - return mTalkgroupId; - } - - /** - * From Radio Unit - */ - public Identifier getSourceAddress() - { - if(mSourceAddress == null) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceAddress; - } - - /** - * No ICD ... anyone? - */ - public boolean isStealthMode() - { - return getMessage().get(SM + getOffset()); - } - - /** - * No ICD ... anyone? - */ - public boolean isTG() - { - return getMessage().get(TG + getOffset()); - } - - /** - * No ICD ... anyone? - * @return - */ - public long getTransmitTime() - { - return getMessage().getInt(TRANSMIT_TIME, getOffset()) * 100; //No ICD ... not sure if multiplier is correct - } - - /** - * Encryption key id to use for the callback - */ - public int getEncryptionKeyId() - { - return getMessage().getInt(KEY_ID, getOffset()); - } - - /** - * Encryption algorithm to use for the call-back - */ - public Encryption getEncryption() - { - return Encryption.fromValue(getMessage().getInt(ALGORITHM_ID, getOffset())); - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceAddress()); - mIdentifiers.add(getTalkgroupId()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandExtended.java deleted file mode 100644 index b892ab7e8..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandExtended.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; -import java.util.List; - -/** - * Radio unit monitor command - */ -public class RadioUnitMonitorCommandExtended extends MacStructure -{ - private static final int[] TRANSMIT_TIME = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int SILENT_MONITOR = 24; //?? - private static final int[] TRANSMIT_MULTIPLIER = {30, 31}; - private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55}; - - private static final int[] SOURCE_WACN = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, - 73, 74, 75}; - private static final int[] SOURCE_SYSTEM = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] SOURCE_ADDRESS = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, - 104, 105, 106, 107, 108, 109, 110, 111}; - - private List mIdentifiers; - private Identifier mTargetAddress; - private APCO25FullyQualifiedRadioIdentifier mSourceSuid; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public RadioUnitMonitorCommandExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceSuid()); - sb.append(" TO:").append(getTargetAddress()); - - if(isSilentMonitor()) - { - sb.append(" SILENT MONITORING"); - } - sb.append(" TIME:").append(getTransmitTime()); - sb.append(" MULTIPLIER:").append(getTransmitMultiplier()); - return sb.toString(); - } - - /** - * Indicates if the target radio should not indicate to the user that the radio is being monitored. - */ - public boolean isSilentMonitor() - { - return getMessage().get(SILENT_MONITOR + getOffset()); - } - - /** - * Transmit time. - */ - public int getTransmitTime() - { - return getMessage().getInt(TRANSMIT_TIME, getOffset()); - } - - /** - * Multiplier for transmit time. - */ - public int getTransmitMultiplier() - { - return getMessage().getInt(TRANSMIT_MULTIPLIER, getOffset()); - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - /** - * From Radio Unit - */ - public APCO25FullyQualifiedRadioIdentifier getSourceSuid() - { - if(mSourceSuid == null) - { - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(getMessage().getInt(SOURCE_WACN, getOffset()), - getMessage().getInt(SOURCE_SYSTEM, getOffset()), getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceSuid; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceSuid()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandExtendedLCCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandExtendedLCCH.java new file mode 100644 index 000000000..65b334313 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandExtendedLCCH.java @@ -0,0 +1,155 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Radio unit monitor command extended LCCH + */ +public class RadioUnitMonitorCommandExtendedLCCH extends MacStructureMultiFragment +{ + private static final IntField TRANSMIT_TIME = IntField.length8(OCTET_4_BIT_24); + private static final int SILENT_MONITOR = 32; + private static final IntField TRANSMIT_MULTIPLIER = IntField.range(38, 39); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_6_BIT_40); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_9_BIT_64); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_11_BIT_80 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_13_BIT_96); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_16_BIT_120); + private static final IntField FRAGMENT_0_TARGET_SUID_WACN = IntField.length20(OCTET_9_BIT_64); + private static final IntField FRAGMENT_0_TARGET_SUID_SYSTEM = IntField.length12(OCTET_11_BIT_80 + 4); + private static final IntField FRAGMENT_0_TARGET_SUID_ID = IntField.length24(OCTET_13_BIT_96); + + private APCO25FullyQualifiedRadioIdentifier mTargetSUID; + private APCO25FullyQualifiedRadioIdentifier mSourceSUID; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RadioUnitMonitorCommandExtendedLCCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceSUID()); + + if(getTargetSUID() != null) + { + sb.append(" TO:").append(getTargetSUID()); + } + + if(isSilentMonitor()) + { + sb.append(" SILENT MONITORING"); + } + sb.append(" TIME:").append(getTransmitTime()); + sb.append(" MULTIPLIER:").append(getTransmitMultiplier()); + return sb.toString(); + } + + /** + * Indicates if the target radio should not indicate to the user that the radio is being monitored. + */ + public boolean isSilentMonitor() + { + return getMessage().get(SILENT_MONITOR + getOffset()); + } + + /** + * Transmit time. + */ + public int getTransmitTime() + { + return getInt(TRANSMIT_TIME); + } + + /** + * Multiplier for transmit time. + */ + public int getTransmitMultiplier() + { + return getInt(TRANSMIT_MULTIPLIER); + } + + /** + * To Talkgroup + */ + public Identifier getTargetSUID() + { + if(mTargetSUID == null && hasFragment(0)) + { + int address = getInt(TARGET_ADDRESS); + int wacn = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_WACN); + int system = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_SYSTEM); + int id = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_ID); + mTargetSUID = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetSUID; + } + + /** + * From Radio Unit + */ + public APCO25FullyQualifiedRadioIdentifier getSourceSUID() + { + if(mSourceSUID == null) + { + int address = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSourceSUID = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mSourceSUID; + } + + @Override + public List getIdentifiers() + { + //Note: this has to be dynamic with multi-fragment messages. + List identifiers = new ArrayList<>(); + identifiers.add(getSourceSUID()); + + if(getTargetSUID() != null) + { + identifiers.add(getTargetSUID()); + } + + return identifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandExtendedVCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandExtendedVCH.java new file mode 100644 index 000000000..45643d6f4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorCommandExtendedVCH.java @@ -0,0 +1,99 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Radio unit monitor command extended VCH + */ +public class RadioUnitMonitorCommandExtendedVCH extends RadioUnitMonitorCommand +{ + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_8_BIT_56); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_10_BIT_72 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_12_BIT_88); + private List mIdentifiers; + private APCO25FullyQualifiedRadioIdentifier mSourceSuid; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RadioUnitMonitorCommandExtendedVCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceSuid()); + sb.append(" TO:").append(getTargetAddress()); + + if(isSilentMonitor()) + { + sb.append(" SILENT MONITORING"); + } + sb.append(" TIME:").append(getTransmitTime()); + sb.append(" MULTIPLIER:").append(getTransmitMultiplier()); + return sb.toString(); + } + + /** + * From Radio Unit + */ + public APCO25FullyQualifiedRadioIdentifier getSourceSuid() + { + if(mSourceSuid == null) + { + int wacn = getMessage().getInt(SOURCE_SUID_WACN, getOffset()); + int system = getMessage().getInt(SOURCE_SUID_SYSTEM, getOffset()); + int id = getMessage().getInt(SOURCE_SUID_ID, getOffset()); + //Fully qualified, but not aliased - reuse the ID as the persona. + mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceSuid; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceSuid()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorEnhancedCommandAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorEnhancedCommandAbbreviated.java new file mode 100644 index 000000000..9726f3e12 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorEnhancedCommandAbbreviated.java @@ -0,0 +1,180 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.reference.Encryption; +import java.util.ArrayList; +import java.util.List; + +/** + * Radio unit monitor command - enhanced format. Commands the target radio to initiate a group or unit-to-unit call to + * either the source address to or the talkgroup, either clear or encrypted + */ +public class RadioUnitMonitorEnhancedCommandAbbreviated extends MacStructure +{ + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_2_BIT_8); + private static final IntField GROUP_ID = IntField.length24(OCTET_5_BIT_32); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_7_BIT_48); + private static final int SM = 72; //Silent Mode + private static final int TG = 73; //Talkgroup (true) or Radio (false) mode + private static final IntField TRANSMIT_TIME = IntField.length8(OCTET_11_BIT_80); + private static final IntField KEY_ID = IntField.length16(OCTET_12_BIT_88); + private static final IntField ALGORITHM_ID = IntField.length8(OCTET_14_BIT_104); + + private List mIdentifiers; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RadioUnitMonitorEnhancedCommandAbbreviated(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" CALL ").append(isTalkgroupMode() ? " TALKGROUP:" : " RADIO:"); + sb.append(getSourceAddress()); + + if(isSilentMode()) + { + sb.append(" USE SILENT MODE"); + } + + long transmitTime = getTransmitTime(); + + if(transmitTime > 0) + { + sb.append(" TRANSMIT TIME:").append(transmitTime).append("secs"); + } + + Encryption encryption = getEncryption(); + + if(encryption != Encryption.UNENCRYPTED) + { + sb.append(" USE ENCRYPTION:").append(encryption); + sb.append(" KEY:").append(getEncryptionKeyId()); + } + + return sb.toString(); + } + + /** + * Silent Mode (true) or non-silent (false) + */ + public boolean isSilentMode() + { + return getMessage().get(SM + getOffset()); + } + + /** + * Indicates if the radio should call the group ID or the radio specified in this message. + */ + public boolean isTalkgroupMode() + { + return getMessage().get(TG + getOffset()); + } + + /** + * To radio address of the unit to be monitored + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Source Radio or Talkgroup that will monitor the targeted radio. + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + if(isTalkgroupMode()) + { + mSourceAddress = APCO25Talkgroup.create(getInt(GROUP_ID)); + } + else + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + } + + return mSourceAddress; + } + + /** + * Transmit time in seconds. + */ + public long getTransmitTime() + { + return getInt(TRANSMIT_TIME); + } + + /** + * Encryption key id to use for the callback + */ + public int getEncryptionKeyId() + { + return getInt(KEY_ID); + } + + /** + * Encryption algorithm to use for the call-back + */ + public Encryption getEncryption() + { + return Encryption.fromValue(getInt(ALGORITHM_ID)); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorEnhancedCommandExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorEnhancedCommandExtended.java new file mode 100644 index 000000000..f7e6c7b15 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RadioUnitMonitorEnhancedCommandExtended.java @@ -0,0 +1,229 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25FullyQualifiedTalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.reference.Encryption; +import java.util.ArrayList; +import java.util.List; + +/** + * Radio unit monitor enhance command - extended + */ +public class RadioUnitMonitorEnhancedCommandExtended extends MacStructureMultiFragment +{ + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_4_BIT_24); + private static final IntField SOURCE_SUID_WACN = IntField.range(OCTET_7_BIT_48, OCTET_7_BIT_48 + 20); + private static final IntField SOURCE_SUID_SYSTEM = IntField.range(68, 79); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_11_BIT_80); + private static final int SM = OCTET_14_BIT_104; //Stealth Mode + private static final int TG = OCTET_14_BIT_104 + 1; //Talkgroup Mode + private static final IntField TRANSMIT_TIME = IntField.length8(OCTET_15_BIT_112); + private static final IntField KEY_ID = IntField.length16(OCTET_16_BIT_120); + private static final IntField ALGORITHM_ID = IntField.length8(OCTET_18_BIT_136); + + private static final IntField FRAGMENT_0_SOURCE_ADDRESS = IntField.length24(OCTET_3_BIT_16); + private static final IntField FRAGMENT_0_TARGET_SUID_WACN = IntField.range(OCTET_6_BIT_40, OCTET_6_BIT_40 + 20); + private static final IntField FRAGMENT_0_TARGET_SUID_SYSTEM = IntField.range(60, 71); + private static final IntField FRAGMENT_0_TARGET_SUID_ID = IntField.length24(OCTET_10_BIT_72); + private static final IntField FRAGMENT_0_SGID_WACN = IntField.range(OCTET_13_BIT_96, OCTET_13_BIT_96 + 20); + private static final IntField FRAGMENT_0_SGID_SYSTEM = IntField.range(116, 127); + private static final IntField FRAGMENT_0_SGID_ID = IntField.length24(OCTET_17_BIT_128); + + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; + private APCO25FullyQualifiedTalkgroupIdentifier mSourceGroup; + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RadioUnitMonitorEnhancedCommandExtended(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + + if(isTalkgroupMode()) + { + if(getSourceGroup() != null) + { + sb.append(" CALL TALKGROUP:").append(getSourceGroup()); + } + } + else + { + if(getSourceAddress() != null) + { + sb.append(" CALL RADIO:").append(getSourceAddress()); + } + } + + if(isSilentMode()) + { + sb.append(" USE SILENT MODE"); + } + + long transmitTime = getTransmitTime(); + + if(transmitTime > 0) + { + sb.append(" TRANSMIT TIME:").append(transmitTime).append("secs"); + } + + Encryption encryption = getEncryption(); + + if(encryption != Encryption.UNENCRYPTED) + { + sb.append(" USE ENCRYPTION:").append(encryption); + sb.append(" KEY:").append(getEncryptionKeyId()); + } + + return sb.toString(); + } + + /** + * Target radio to be monitored + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasFragment(0)) + { + int address = getInt(TARGET_ADDRESS); + int wacn = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_WACN); + int system = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_SYSTEM); + int id = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetAddress; + } + + /** + * Talkgroup that the targeted radio should call, if talkgroup mode is specified. + */ + public Identifier getSourceGroup() + { + if(mSourceGroup == null && hasFragment(0)) + { + int wacn = getFragment(0).getInt(FRAGMENT_0_SGID_WACN); + int system = getFragment(0).getInt(FRAGMENT_0_SGID_SYSTEM); + int id = getFragment(0).getInt(FRAGMENT_0_SGID_ID); + mSourceGroup = APCO25FullyQualifiedTalkgroupIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceGroup; + } + + /** + * Radio that the targeted radio should call, if talkgroup mode is not specified. + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null && hasFragment(0)) + { + int address = getFragment(0).getInt(FRAGMENT_0_SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mSourceAddress; + } + + /** + * Silent mode monitoring. + */ + public boolean isSilentMode() + { + return getMessage().get(SM + getOffset()); + } + + /** + * Talkgroup mode or radio mode. + */ + public boolean isTalkgroupMode() + { + return getMessage().get(TG + getOffset()); + } + + /** + * Transmit time in seconds. + */ + public long getTransmitTime() + { + return getMessage().getInt(TRANSMIT_TIME, getOffset()); + } + + /** + * Encryption key id to use for the callback + */ + public int getEncryptionKeyId() + { + return getInt(KEY_ID); + } + + /** + * Encryption algorithm to use for the call-back + */ + public Encryption getEncryption() + { + return Encryption.fromValue(getInt(ALGORITHM_ID)); + } + + @Override + public List getIdentifiers() + { + //Note: this has to be dynamically constructed each time to account for late-add continuation fragments. + List identifiers = new ArrayList<>(); + + if(getTargetAddress() != null) + { + identifiers.add(getTargetAddress()); + } + + if(getSourceGroup() != null) + { + identifiers.add(getSourceGroup()); + } + + if(getSourceAddress() != null) + { + identifiers.add(getSourceAddress()); + } + + return identifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcast.java new file mode 100644 index 000000000..dce1f61d2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcast.java @@ -0,0 +1,106 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Lra; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; +import io.github.dsheirer.module.decode.p25.identifier.APCO25System; + +/** + * RFSS status broadcast base implementation + */ +public abstract class RfssStatusBroadcast extends MacStructure +{ + private static final IntField LRA = IntField.length8(OCTET_2_BIT_8); + private static final int R = 18; + private static final int A = 19; + private static final IntField SYSTEM_ID = IntField.length12(OCTET_3_BIT_16 + 4); + private static final IntField RFSS_ID = IntField.length8(OCTET_5_BIT_32); + private static final IntField SITE_ID = IntField.length8(OCTET_6_BIT_40); + private Identifier mLRA; + private Identifier mSystem; + private Identifier mRFSS; + private Identifier mSite; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RfssStatusBroadcast(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + public String getNetworkConnectionStatus() + { + return getMessage().get(A + getOffset()) ? "NETWORK CONNECTED" : "NETWORK DISCONNECTED"; + } + + public String getRoamingRadioReaccessMethod() + { + return getMessage().get(R + getOffset()) ? "ROAMING RADIO REACCESS ON LCCH" : "ROAMING RADIO REACCESS ON VCH"; + } + + public Identifier getLRA() + { + if(mLRA == null) + { + mLRA = APCO25Lra.create(getInt(LRA)); + } + + return mLRA; + } + + public Identifier getRFSS() + { + if(mRFSS == null) + { + mRFSS = APCO25Rfss.create(getInt(RFSS_ID)); + } + + return mRFSS; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getInt(SITE_ID)); + } + + return mSite; + } + + public Identifier getSystem() + { + if(mSystem == null) + { + mSystem = APCO25System.create(getInt(SYSTEM_ID)); + } + + return mSystem; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastExplicit.java new file mode 100644 index 000000000..17d51c7a3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastExplicit.java @@ -0,0 +1,121 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +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.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * RFSS status broadcast explicit + */ +public class RfssStatusBroadcastExplicit extends RfssStatusBroadcast implements IFrequencyBandReceiver +{ + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_48); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_9_BIT_64); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_9_BIT_64 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_11_BIT_80); + + private List mIdentifiers; + private APCO25Channel mChannel; + private SystemServiceClass mSystemServiceClass; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RfssStatusBroadcastExplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" RFSS:").append(getRFSS()); + sb.append(" SITE:").append(getSite()); + sb.append(" LRA:").append(getLRA()); + sb.append(" CHANNEL:").append(getChannel()); + sb.append(" SERVICES:").append(getSystemServiceClass().getServices()); + return sb.toString(); + } + + /** + * Control channel. This will be a phase 1 channel, even though it's being broadcast on a Phase II channel. + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); + } + + return mChannel; + } + + public SystemServiceClass getSystemServiceClass() + { + if(mSystemServiceClass == null) + { + mSystemServiceClass = SystemServiceClass.create(getInt(SERVICE_CLASS)); + } + + return mSystemServiceClass; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLRA()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getRFSS()); + mIdentifiers.add(getSite()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastImplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastImplicit.java new file mode 100644 index 000000000..95e62b04f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RfssStatusBroadcastImplicit.java @@ -0,0 +1,119 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * RFSS status broadcast implicit + */ +public class RfssStatusBroadcastImplicit extends RfssStatusBroadcast implements IFrequencyBandReceiver +{ + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_48); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField SERVICE_CLASS = IntField.length8(OCTET_9_BIT_64); + + private List mIdentifiers; + private APCO25Channel mChannel; + private SystemServiceClass mSystemServiceClass; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RfssStatusBroadcastImplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" SYSTEM:").append(getSystem()); + sb.append(" RFSS:").append(getRFSS()); + sb.append(" SITE:").append(getSite()); + sb.append(" LRA:").append(getLRA()); + sb.append(" CHANNEL:").append(getChannel()); + sb.append(" SERVICES:").append(getSystemServiceClass().getServices()); + sb.append(" ").append(getNetworkConnectionStatus()); + sb.append(" ").append(getRoamingRadioReaccessMethod()); + return sb.toString(); + } + + /** + * Control channel. This will be a phase 1 control channel even though it's being broadcast on a Phase 2 channel. + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + public SystemServiceClass getSystemServiceClass() + { + if(mSystemServiceClass == null) + { + mSystemServiceClass = SystemServiceClass.create(getInt(SERVICE_CLASS)); + } + + return mSystemServiceClass; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getLRA()); + mIdentifiers.add(getSystem()); + mIdentifiers.add(getRFSS()); + mIdentifiers.add(getSite()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RoamingAddressCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RoamingAddressCommand.java new file mode 100644 index 000000000..d11cf2b6c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RoamingAddressCommand.java @@ -0,0 +1,95 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.reference.StackOperation; +import java.util.ArrayList; +import java.util.List; + +/** + * Roaming address command + */ +public class RoamingAddressCommand extends MacStructure +{ + private static final IntField STACK_OPERATION = IntField.length8(OCTET_3_BIT_16); + private static final IntField TARGET_SUID_WACN = IntField.length20(OCTET_4_BIT_24); + private static final IntField TARGET_SUID_SYSTEM = IntField.length12(OCTET_6_BIT_40 + 4); + private static final IntField TARGET_SUID_ID = IntField.length24(OCTET_8_BIT_56); + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RoamingAddressCommand(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" ").append(getStackOperation()).append(" ROAMING ADDRESS STACK"); + + return sb.toString(); + } + + public StackOperation getStackOperation() + { + return StackOperation.fromValue(getInt(STACK_OPERATION)); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + int wacn = getInt(TARGET_SUID_WACN); + int system = getInt(TARGET_SUID_SYSTEM); + int id = getInt(TARGET_SUID_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(id, wacn, system, id); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RoamingAddressUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RoamingAddressUpdate.java new file mode 100644 index 000000000..abe35379c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/RoamingAddressUpdate.java @@ -0,0 +1,122 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Roaming address update + */ +public class RoamingAddressUpdate extends MacStructure +{ + private static final int LAST_MESSAGE_INDICATOR = 16; + private static final IntField MESSAGE_SEQUENCE_NUMBER = IntField.length4(OCTET_3_BIT_16 + 4); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_4_BIT_24); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_7_BIT_48); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_9_BIT_64 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_11_BIT_80); + private List mIdentifiers; + private Identifier mTargetAddress; + private APCO25FullyQualifiedRadioIdentifier mSourceSuid; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public RoamingAddressUpdate(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceSuid()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" MESSAGE SEQUENCE NUMBER:").append(getMessageSequenceNumber()); + if(isLastMessage()) + { + sb.append(" - LAST MESSAGE"); + } + return sb.toString(); + } + + public boolean isLastMessage() + { + return getMessage().get(LAST_MESSAGE_INDICATOR + getOffset()); + } + + public int getMessageSequenceNumber() + { + return getInt(MESSAGE_SEQUENCE_NUMBER); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * From Radio Unit + */ + public APCO25FullyQualifiedRadioIdentifier getSourceSuid() + { + if(mSourceSuid == null) + { + int wacn = getMessage().getInt(SOURCE_SUID_WACN, getOffset()); + int system = getMessage().getInt(SOURCE_SUID_SYSTEM, getOffset()); + int id = getMessage().getInt(SOURCE_SUID_ID, getOffset()); + //Fully qualified, but not aliased - reuse the ID as the persona. + mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceSuid; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceSuid()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelAnnouncement.java new file mode 100644 index 000000000..467554d90 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelAnnouncement.java @@ -0,0 +1,147 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +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.IFrequencyBandReceiver; +import java.util.Collections; +import java.util.List; + +/** + * SNDCP data channel announcement + */ +public class SNDCPDataChannelAnnouncement extends MacStructureDataService implements IFrequencyBandReceiver +{ + private static final int AUTONOMOUS_ACCESS_FLAG = 16; + private static final int REQUESTED_ACCESS_FLAG = 17; + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_4_BIT_24); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_4_BIT_24 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_40); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_40 + 4); + private static final IntField DATA_ACCESS_CONTROL = IntField.length16(OCTET_8_BIT_56); + private List mIdentifiers; + private APCO25Channel mChannel; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public SNDCPDataChannelAnnouncement(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" CHAN:").append(getChannel()); + if(isAutonomousAccess() && isRequestedAccess()) + { + sb.append(" AUTONOMOUS/REQUESTED-ACCESS"); + } + else if(isAutonomousAccess()) + { + sb.append(" AUTONOMOUS-ACCESS"); + } + else if(isRequestedAccess()) + { + sb.append(" REQUESTED-ACCESS"); + } + sb.append(" DAC:").append(getDataAccessControl()); + sb.append(" ").append(getServiceOptions()); + return sb.toString(); + } + + public boolean isAutonomousAccess() + { + return getMessage().get(AUTONOMOUS_ACCESS_FLAG + getOffset()); + } + + public boolean isRequestedAccess() + { + return getMessage().get(REQUESTED_ACCESS_FLAG + getOffset()); + } + + /** + * Channel + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + if(isExplicitChannel()) + { + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); + } + else + { + mChannel = APCO25Channel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER)); + } + } + + return mChannel; + } + + /** + * Indicates if the channel is an explicit channel, meaning that there are separate downlink + * and uplink channel numbers included. When false, the channel number is the same for both + * uplink and downlink. + * + * @return true if this is an explicit channel. + */ + private boolean isExplicitChannel() + { + return getInt(RECEIVE_CHANNEL_NUMBER) != 4095; + } + + public int getDataAccessControl() + { + return getInt(DATA_ACCESS_CONTROL); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = Collections.singletonList(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelAnnouncementExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelAnnouncementExplicit.java deleted file mode 100644 index bfb5c7acb..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelAnnouncementExplicit.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; -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.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; - -import java.util.ArrayList; -import java.util.List; - -/** - * SNDCP data channel announcement - explicit format - */ -public class SNDCPDataChannelAnnouncementExplicit extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int AUTONOMOUS_ACCESS_FLAG = 16; - private static final int REQUESTED_ACCESS_FLAG = 17; - private static final int[] TRANSMIT_FREQUENCY_BAND = {24, 25, 26, 27}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] RECEIVE_FREQUENCY_BAND = {40, 41, 42, 43}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] DATA_ACCESS_CONTROL = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - - private List mIdentifiers; - private APCO25Channel mChannel; - private TalkgroupIdentifier mTargetAddress; - private DataServiceOptions mServiceOptions; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public SNDCPDataChannelAnnouncementExplicit(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" CHAN:").append(getChannel()); - if(isAutonomousAccess() && isRequestedAccess()) - { - sb.append(" AUTONOMOUS/REQUESTED-ACCESS"); - } - else if(isAutonomousAccess()) - { - sb.append(" AUTONOMOUS-ACCESS"); - } - else if(isRequestedAccess()) - { - sb.append(" REQUESTED-ACCESS"); - } - sb.append(" DAC:").append(getDataAccessControl()); - sb.append(" ").append(getServiceOptions()); - return sb.toString(); - } - - public boolean isAutonomousAccess() - { - return getMessage().get(AUTONOMOUS_ACCESS_FLAG + getOffset()); - } - - public boolean isRequestedAccess() - { - return getMessage().get(REQUESTED_ACCESS_FLAG + getOffset()); - } - - - /** - * Voice channel service options - */ - public DataServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new DataServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - - /** - * Channel - */ - public APCO25Channel getChannel() - { - if(mChannel == null) - { - if(isExplicitChannel()) - { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset())); - } - else - { - mChannel = APCO25Channel.create(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset())); - } - } - - return mChannel; - } - - /** - * Indicates if the channel is an explicit channel, meaning that there are separate downlink - * and uplink channel numbers included. When false, the channel number is the same for both - * uplink and downlink. - * - * @return true if this is an explicit channel. - */ - private boolean isExplicitChannel() - { - return getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset()) != 4095; - } - - public int getDataAccessControl() - { - return getMessage().getInt(DATA_ACCESS_CONTROL, getOffset()); - } - - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getChannel()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelGrant.java index fd3effb1c..9f9ac7df0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelGrant.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataChannelGrant.java @@ -1,57 +1,51 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; 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.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; - +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** * SNDCP data channel grant */ -public class SNDCPDataChannelGrant extends MacStructure implements IFrequencyBandReceiver +public class SNDCPDataChannelGrant extends MacStructureDataService + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] TRANSMIT_FREQUENCY_BAND = {16, 17, 18, 19}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] RECEIVE_FREQUENCY_BAND = {32, 33, 34, 35}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_3_BIT_16); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_3_BIT_16 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_5_BIT_32); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_48); private List mIdentifiers; private APCO25Channel mChannel; private Identifier mTargetAddress; - private DataServiceOptions mServiceOptions; /** * Constructs the message @@ -77,19 +71,6 @@ public String toString() return sb.toString(); } - /** - * Voice channel service options - */ - public DataServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new DataServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - /** * Channel */ @@ -97,10 +78,8 @@ public APCO25Channel getChannel() { if(mChannel == null) { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset())); + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); } return mChannel; @@ -113,12 +92,20 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; } + /** + * Implements the channel grant detail provider interface but always returns null. + */ + public Identifier getSourceAddress() + { + return null; + } + @Override public List getIdentifiers() { @@ -135,8 +122,6 @@ public List getIdentifiers() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataPageRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataPageRequest.java index 18233e6e5..e26364290 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataPageRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SNDCPDataPageRequest.java @@ -1,49 +1,40 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; - -import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** * SNDCP data page request */ -public class SNDCPDataPageRequest extends MacStructure +public class SNDCPDataPageRequest extends MacStructureDataService { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] DATA_ACCESS_CONTROL = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - + private static final IntField DATA_ACCESS_CONTROL = IntField.length16(OCTET_3_BIT_16); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_5_BIT_32); private List mIdentifiers; private Identifier mTargetAddress; - private DataServiceOptions mServiceOptions; /** * Constructs the message @@ -68,19 +59,6 @@ public String toString() return sb.toString(); } - /** - * Voice channel service options - */ - public DataServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new DataServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - /** * To Talkgroup */ @@ -88,7 +66,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -99,8 +77,7 @@ public List getIdentifiers() { if(mIdentifiers == null) { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); + mIdentifiers = Collections.singletonList(getTargetAddress()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcast.java new file mode 100644 index 000000000..8748ac432 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcast.java @@ -0,0 +1,69 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; +import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; + +/** + * Secondary control channel broadcast base implementation + */ +public abstract class SecondaryControlChannelBroadcast extends MacStructure +{ + private static final IntField RFSS = IntField.length8(OCTET_2_BIT_8); + private static final IntField SITE = IntField.length8(OCTET_3_BIT_16); + + private Identifier mRfss; + private Identifier mSite; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public SecondaryControlChannelBroadcast(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + public Identifier getRfss() + { + if(mRfss == null) + { + mRfss = APCO25Rfss.create(getInt(RFSS)); + } + + return mRfss; + } + + public Identifier getSite() + { + if(mSite == null) + { + mSite = APCO25Site.create(getInt(SITE)); + } + + return mSite; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastAbbreviated.java deleted file mode 100644 index 19c0c631e..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastAbbreviated.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; -import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; -import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - -import java.util.ArrayList; -import java.util.List; - -/** - * Secondary control channel broadcast - abbreviated format - */ -public class SecondaryControlChannelBroadcastAbbreviated extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] RFSS = {8,9,10,11,12,13,14,15}; - private static final int[] SITE = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] FREQUENCY_BAND_A = {24, 25, 26, 27}; - private static final int[] CHANNEL_NUMBER_A = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SYSTEM_SERVICE_CLASS_A = {40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] FREQUENCY_BAND_B = {48, 49, 50, 51}; - private static final int[] CHANNEL_NUMBER_B = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SYSTEM_SERVICE_CLASS_B = {64, 65, 66, 67, 68, 69, 70, 71}; - - private Identifier mRfss; - private Identifier mSite; - private IChannelDescriptor mChannelA; - private IChannelDescriptor mChannelB; - private SystemServiceClass mSystemServiceClassA; - private SystemServiceClass mSystemServiceClassB; - private List mIdentifiers; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public SecondaryControlChannelBroadcastAbbreviated(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" RFSS:").append(getRfss()); - sb.append(" SITE:").append(getSite()); - sb.append(" CHAN A:").append(getChannelA()); - sb.append(" SERVICE OPTIONS:").append(getSystemServiceClassA()); - if(hasChannelB()) - { - sb.append(" CHAN B:").append(getChannelB()); - sb.append(" SERVICE OPTIONS:").append(getSystemServiceClassB()); - } - return sb.toString(); - } - - public Identifier getRfss() - { - if(mRfss == null) - { - mRfss = APCO25Rfss.create(getMessage().getInt(RFSS, getOffset())); - } - - return mRfss; - } - - public Identifier getSite() - { - if(mSite == null) - { - mSite = APCO25Site.create(getMessage().getInt(SITE, getOffset())); - } - - return mSite; - } - - public IChannelDescriptor getChannelA() - { - if(mChannelA == null) - { - mChannelA = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_A, getOffset()), - getMessage().getInt(CHANNEL_NUMBER_A, getOffset())); - } - - return mChannelA; - } - - public SystemServiceClass getSystemServiceClassA() - { - if(mSystemServiceClassA == null) - { - mSystemServiceClassA = new SystemServiceClass(getMessage().getInt(SYSTEM_SERVICE_CLASS_A, getOffset())); - } - - return mSystemServiceClassA; - } - - private boolean hasChannelB() - { - return getMessage().getInt(CHANNEL_NUMBER_A, getOffset()) != getMessage().getInt(CHANNEL_NUMBER_B, getOffset()) && - getMessage().getInt(SYSTEM_SERVICE_CLASS_B, getOffset()) != 0; - } - - public IChannelDescriptor getChannelB() - { - if(hasChannelB() && mChannelB == null) - { - mChannelB = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND_B, getOffset()), - getMessage().getInt(CHANNEL_NUMBER_B, getOffset())); - } - - return mChannelB; - } - - public SystemServiceClass getSystemServiceClassB() - { - if(mSystemServiceClassB == null) - { - mSystemServiceClassB = new SystemServiceClass(getMessage().getInt(SYSTEM_SERVICE_CLASS_B, getOffset())); - } - - return mSystemServiceClassB; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getSite()); - mIdentifiers.add(getRfss()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - List channels = new ArrayList<>(); - channels.add(getChannelA()); - - if(hasChannelB()) - { - channels.add(getChannelB()); - } - return channels; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastExplicit.java index 86e24fffd..22cc2effe 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastExplicit.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastExplicit.java @@ -1,55 +1,46 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Rfss; -import io.github.dsheirer.module.decode.p25.identifier.APCO25Site; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; - import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Secondary control channel broadcast - explicit channel format + * Secondary control channel broadcast explicit */ -public class SecondaryControlChannelBroadcastExplicit extends MacStructure implements IFrequencyBandReceiver +public class SecondaryControlChannelBroadcastExplicit extends SecondaryControlChannelBroadcast implements IFrequencyBandReceiver { - private static final int[] RFSS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] SITE = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] TRANSMIT_FREQUENCY_BAND = {24, 25, 26, 27}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] RECEIVE_FREQUENCY_BAND = {40, 41, 42, 43}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}; - private static final int[] SYSTEM_SERVICE_CLASS = {56, 57, 58, 59, 60, 61, 62, 63}; + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_4_BIT_24); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_4_BIT_24 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_40); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_40 + 4); + private static final IntField SYSTEM_SERVICE_CLASS = IntField.length8(OCTET_8_BIT_56); - private Identifier mRfss; - private Identifier mSite; private IChannelDescriptor mChannel; private SystemServiceClass mSystemServiceClass; private List mIdentifiers; @@ -79,34 +70,12 @@ public String toString() return sb.toString(); } - public Identifier getRfss() - { - if(mRfss == null) - { - mRfss = APCO25Rfss.create(getMessage().getInt(RFSS, getOffset())); - } - - return mRfss; - } - - public Identifier getSite() - { - if(mSite == null) - { - mSite = APCO25Site.create(getMessage().getInt(SITE, getOffset())); - } - - return mSite; - } - public IChannelDescriptor getChannel() { if(mChannel == null) { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset())); + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); } return mChannel; @@ -116,7 +85,7 @@ public SystemServiceClass getSystemServiceClass() { if(mSystemServiceClass == null) { - mSystemServiceClass = new SystemServiceClass(getMessage().getInt(SYSTEM_SERVICE_CLASS, getOffset())); + mSystemServiceClass = new SystemServiceClass(getInt(SYSTEM_SERVICE_CLASS)); } return mSystemServiceClass; @@ -138,8 +107,6 @@ public List getIdentifiers() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastImplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastImplicit.java new file mode 100644 index 000000000..091ee00d6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SecondaryControlChannelBroadcastImplicit.java @@ -0,0 +1,150 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass; +import java.util.ArrayList; +import java.util.List; + +/** + * Secondary control channel broadcast implicit + */ +public class SecondaryControlChannelBroadcastImplicit extends SecondaryControlChannelBroadcast implements IFrequencyBandReceiver +{ + private static final IntField FREQUENCY_BAND_1 = IntField.length4(OCTET_4_BIT_24); + private static final IntField CHANNEL_NUMBER_1 = IntField.length12(OCTET_4_BIT_24 + 4); + private static final IntField SYSTEM_SERVICE_CLASS_1 = IntField.length8(OCTET_6_BIT_40); + private static final IntField FREQUENCY_BAND_2 = IntField.length4(OCTET_7_BIT_48); + private static final IntField CHANNEL_NUMBER_2 = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField SYSTEM_SERVICE_CLASS_2 = IntField.length8(OCTET_9_BIT_64); + + private IChannelDescriptor mChannel1; + private IChannelDescriptor mChannel2; + private SystemServiceClass mSystemServiceClass1; + private SystemServiceClass mSystemServiceClass2; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public SecondaryControlChannelBroadcastImplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" RFSS:").append(getRfss()); + sb.append(" SITE:").append(getSite()); + sb.append(" CHAN 1:").append(getChannel1()); + sb.append(" SERVICE OPTIONS:").append(getSystemServiceClass1()); + if(hasChannelB()) + { + sb.append(" CHAN 2:").append(getChannel2()); + sb.append(" SERVICE OPTIONS:").append(getSystemServiceClass2()); + } + return sb.toString(); + } + + public IChannelDescriptor getChannel1() + { + if(mChannel1 == null) + { + mChannel1 = APCO25Channel.create(getInt(FREQUENCY_BAND_1), getInt(CHANNEL_NUMBER_1)); + } + + return mChannel1; + } + + public SystemServiceClass getSystemServiceClass1() + { + if(mSystemServiceClass1 == null) + { + mSystemServiceClass1 = new SystemServiceClass(getInt(SYSTEM_SERVICE_CLASS_1)); + } + + return mSystemServiceClass1; + } + + private boolean hasChannelB() + { + return getInt(CHANNEL_NUMBER_1) != getInt(CHANNEL_NUMBER_2) && getInt(SYSTEM_SERVICE_CLASS_2) != 0; + } + + public IChannelDescriptor getChannel2() + { + if(hasChannelB() && mChannel2 == null) + { + mChannel2 = APCO25Channel.create(getInt(FREQUENCY_BAND_2), getInt(CHANNEL_NUMBER_2)); + } + + return mChannel2; + } + + public SystemServiceClass getSystemServiceClass2() + { + if(mSystemServiceClass2 == null) + { + mSystemServiceClass2 = new SystemServiceClass(getInt(SYSTEM_SERVICE_CLASS_2)); + } + + return mSystemServiceClass2; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSite()); + mIdentifiers.add(getRfss()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannel1()); + + if(hasChannelB()) + { + channels.add(getChannel2()); + } + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryAbbreviated.java index 54e7174be..cd1669f6a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryAbbreviated.java @@ -1,32 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; @@ -35,11 +31,8 @@ */ public class StatusQueryAbbreviated extends MacStructure { - private static final int[] TARGET_ADDRESS = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_2_BIT_8); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_5_BIT_32); private List mIdentifiers; private Identifier mTargetAddress; private Identifier mSourceAddress; @@ -74,7 +67,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -87,7 +80,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryExtended.java deleted file mode 100644 index 36f92816b..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryExtended.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; -import java.util.List; - -/** - * Status query - extended format - */ -public class StatusQueryExtended extends MacStructure -{ - private static final int[] TARGET_ADDRESS = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_WACN = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51}; - private static final int[] SOURCE_SYSTEM = {52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] SOURCE_ADDRESS = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87}; - - private List mIdentifiers; - private Identifier mTargetAddress; - private Identifier mSourceSuid; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public StatusQueryExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceSuid()); - sb.append(" TO:").append(getTargetAddress()); - return sb.toString(); - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - /** - * From Radio Unit - */ - public Identifier getSourceSuid() - { - if(mSourceSuid == null) - { - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(getMessage().getInt(SOURCE_WACN, getOffset()), - getMessage().getInt(SOURCE_SYSTEM, getOffset()), getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceSuid; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceSuid()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryExtendedLCCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryExtendedLCCH.java new file mode 100644 index 000000000..6b04f65a9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryExtendedLCCH.java @@ -0,0 +1,127 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Status query extended LCCH + */ +public class StatusQueryExtendedLCCH extends MacStructureMultiFragment +{ + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_4_BIT_24); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_7_BIT_48); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_9_BIT_64 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_11_BIT_80); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_14_BIT_104); + private static final IntField TARGET_SUID_WACN = IntField.length16(OCTET_17_BIT_128); + private static final IntField FRAGMENT_0_TARGET_SUID_WACN = IntField.length4(OCTET_3_BIT_16); + private static final IntField FRAGMENT_0_TARGET_SUID_SYSTEM = IntField.length12(OCTET_3_BIT_16 + 4); + private static final IntField FRAGMENT_0_TARGET_SUID_ID = IntField.length24(OCTET_5_BIT_32); + + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public StatusQueryExtendedLCCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + if(getSourceAddress() != null) + { + sb.append(" FM:").append(getSourceAddress()); + } + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + return sb.toString(); + } + + /** + * To Radio + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasFragment(0)) + { + int address = getInt(TARGET_ADDRESS); + int wacn = getInt(TARGET_SUID_WACN); + wacn <<= 4; + wacn += getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_WACN); + + int system = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_SYSTEM); + int id = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetAddress; + } + + /** + * From Radio + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + int address = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + //Note: this has to be dynamically constructed each time to account for late-add continuation fragments. + List identifiers = new ArrayList<>(); + identifiers.add(getSourceAddress()); + + if(getTargetAddress() != null) + { + identifiers.add(getTargetAddress()); + } + + return identifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryExtendedVCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryExtendedVCH.java new file mode 100644 index 000000000..dcfe95e35 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusQueryExtendedVCH.java @@ -0,0 +1,109 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Status query extended VCH + */ +public class StatusQueryExtendedVCH extends MacStructure +{ + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_2_BIT_8); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_5_BIT_32); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField SOURCE_SUID_ADDRESS = IntField.length24(OCTET_9_BIT_64); + + private List mIdentifiers; + private Identifier mTargetAddress; + private Identifier mSourceSuid; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public StatusQueryExtendedVCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceSuid()); + sb.append(" TO:").append(getTargetAddress()); + return sb.toString(); + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * From Radio Unit + */ + public Identifier getSourceSuid() + { + if(mSourceSuid == null) + { + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ADDRESS); + //Fully qualified, but not aliased - reuse the ID as the persona. + mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceSuid; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceSuid()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdate.java new file mode 100644 index 000000000..ce2c7fdee --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdate.java @@ -0,0 +1,84 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UnitStatus; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UserStatus; + +/** + * Status update base implementation + */ +public abstract class StatusUpdate extends MacStructure +{ + private static final IntField UNIT_STATUS = IntField.length8(OCTET_3_BIT_16); + private static final IntField USER_STATUS = IntField.length8(OCTET_4_BIT_24); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_5_BIT_32); + private Identifier mUnitStatus; + private Identifier mUserStatus; + private Identifier mTargetAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public StatusUpdate(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null) + { + mUnitStatus = APCO25UnitStatus.create(getInt(UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null) + { + mUserStatus = APCO25UserStatus.create(getInt(USER_STATUS)); + } + + return mUserStatus; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateAbbreviated.java index 10ae42e4c..aaed222c3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateAbbreviated.java @@ -1,53 +1,39 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UnitStatus; -import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UserStatus; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; /** - * Status update - abbreviated format + * Status update abbreviated */ -public class StatusUpdateAbbreviated extends MacStructure +public class StatusUpdateAbbreviated extends StatusUpdate { - private static final int[] UNIT_STATUS = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] USER_STATUS = {24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - private static final int[] SOURCE_ADDRESS = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, - 73, 74, 75, 76, 77, 78, 79}; + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_8_BIT_56); private List mIdentifiers; - private Identifier mUnitStatus; - private Identifier mUserStatus; - private Identifier mTargetAddress; private Identifier mSourceAddress; /** @@ -68,46 +54,13 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getOpcode()); - sb.append(" FM:").append(getSourceAddress()); sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); sb.append(" UNIT:").append(getUnitStatus()); sb.append(" USER:").append(getUserStatus()); return sb.toString(); } - public Identifier getUnitStatus() - { - if(mUnitStatus == null) - { - mUnitStatus = APCO25UnitStatus.create(getMessage().getInt(UNIT_STATUS)); - } - - return mUnitStatus; - } - - public Identifier getUserStatus() - { - if(mUserStatus == null) - { - mUserStatus = APCO25UserStatus.create(getMessage().getInt(USER_STATUS)); - } - - return mUserStatus; - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - /** * From Radio Unit */ @@ -115,7 +68,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateExtended.java deleted file mode 100644 index 116cf3f8f..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateExtended.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UnitStatus; -import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UserStatus; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; -import java.util.List; - -/** - * Status update - abbreviated format - */ -public class StatusUpdateExtended extends MacStructure -{ - private static final int[] UNIT_STATUS = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] USER_STATUS = {24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] TARGET_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - private static final int[] SOURCE_WACN = {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, - 73, 74, 75}; - private static final int[] SOURCE_SYSTEM = {76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] SOURCE_ADDRESS = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, - 104, 105, 106, 107, 108, 109, 110, 111}; - - private List mIdentifiers; - private Identifier mUnitStatus; - private Identifier mUserStatus; - private Identifier mTargetAddress; - private APCO25FullyQualifiedRadioIdentifier mSourceSuid; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public StatusUpdateExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceSuid()); - sb.append(" TO:").append(getTargetAddress()); - sb.append(" UNIT:").append(getUnitStatus()); - sb.append(" USER:").append(getUserStatus()); - return sb.toString(); - } - - public Identifier getUnitStatus() - { - if(mUnitStatus == null) - { - mUnitStatus = APCO25UnitStatus.create(getMessage().getInt(UNIT_STATUS)); - } - - return mUnitStatus; - } - - public Identifier getUserStatus() - { - if(mUserStatus == null) - { - mUserStatus = APCO25UserStatus.create(getMessage().getInt(USER_STATUS)); - } - - return mUserStatus; - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - /** - * From Radio Unit - */ - public Identifier getSourceSuid() - { - if(mSourceSuid == null) - { - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(getMessage().getInt(SOURCE_WACN, getOffset()), - getMessage().getInt(SOURCE_SYSTEM, getOffset()), getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceSuid; - } - - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceSuid()); - mIdentifiers.add(getUnitStatus()); - mIdentifiers.add(getUserStatus()); - } - - return mIdentifiers; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateExtendedLCCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateExtendedLCCH.java new file mode 100644 index 000000000..a1086cbd9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateExtendedLCCH.java @@ -0,0 +1,148 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UnitStatus; +import io.github.dsheirer.module.decode.p25.identifier.status.APCO25UserStatus; +import java.util.ArrayList; +import java.util.List; + +/** + * Status update extended LCCH + */ +public class StatusUpdateExtendedLCCH extends MacStructureMultiFragment +{ + private static final IntField UNIT_STATUS = IntField.length8(OCTET_4_BIT_24); + private static final IntField USER_STATUS = IntField.length8(OCTET_5_BIT_32); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_6_BIT_40); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_9_BIT_64); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_11_BIT_80 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_13_BIT_96); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_16_BIT_120); + private static final IntField FRAGMENT_0_TARGET_SUID_WACN = IntField.length20(OCTET_3_BIT_16); + private static final IntField FRAGMENT_0_TARGET_SUID_SYSTEM = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField FRAGMENT_0_TARGET_SUID_ID = IntField.length24(OCTET_7_BIT_48); + + private Identifier mUnitStatus; + private Identifier mUserStatus; + private APCO25FullyQualifiedRadioIdentifier mTargetSUID; + private APCO25FullyQualifiedRadioIdentifier mSourceSUID; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public StatusUpdateExtendedLCCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + if(getTargetSUID() != null) + { + sb.append(" TO:").append(getTargetSUID()); + } + + sb.append(" FM:").append(getSourceSUID()); + sb.append(" UNIT:").append(getUnitStatus()); + sb.append(" USER:").append(getUserStatus()); + return sb.toString(); + } + + public Identifier getUnitStatus() + { + if(mUnitStatus == null) + { + mUnitStatus = APCO25UnitStatus.create(getInt(UNIT_STATUS)); + } + + return mUnitStatus; + } + + public Identifier getUserStatus() + { + if(mUserStatus == null) + { + mUserStatus = APCO25UserStatus.create(getInt(USER_STATUS)); + } + + return mUserStatus; + } + + /** + * To Talkgroup + */ + public Identifier getTargetSUID() + { + if(mTargetSUID == null && hasFragment(0)) + { + int address = getInt(TARGET_ADDRESS); + int wacn = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_WACN); + int system = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_SYSTEM); + int id = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_ID); + mTargetSUID = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mTargetSUID; + } + + /** + * From Radio Unit + */ + public APCO25FullyQualifiedRadioIdentifier getSourceSUID() + { + if(mSourceSUID == null) + { + int address = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSourceSUID = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mSourceSUID; + } + + @Override + public List getIdentifiers() + { + List identifiers = new ArrayList<>(); + identifiers.add(getSourceSUID()); + + if(getTargetSUID() != null) + { + identifiers.add(getTargetSUID()); + } + + return identifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateExtendedVCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateExtendedVCH.java new file mode 100644 index 000000000..5912333b7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/StatusUpdateExtendedVCH.java @@ -0,0 +1,97 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Status update extended VCH + */ +public class StatusUpdateExtendedVCH extends StatusUpdate +{ + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_8_BIT_56); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_10_BIT_72 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_12_BIT_88); + + private List mIdentifiers; + private APCO25FullyQualifiedRadioIdentifier mSourceSUID; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public StatusUpdateExtendedVCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceSUID()); + sb.append(" UNIT:").append(getUnitStatus()); + sb.append(" USER:").append(getUserStatus()); + return sb.toString(); + } + + /** + * From Radio Unit + */ + public APCO25FullyQualifiedRadioIdentifier getSourceSUID() + { + if(mSourceSUID == null) + { + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + //Fully qualified, but not aliased - reuse the ID as the address. + mSourceSUID = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceSUID; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceSUID()); + mIdentifiers.add(getUnitStatus()); + mIdentifiers.add(getUserStatus()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SynchronizationBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SynchronizationBroadcast.java new file mode 100644 index 000000000..56e285419 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SynchronizationBroadcast.java @@ -0,0 +1,272 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.SimpleTimeZone; +import java.util.TimeZone; + +/** + * Synchronization broadcast + */ +public class SynchronizationBroadcast extends MacStructure +{ + public static final int US_TDMA_UNSYNCHRONIZED_TO_FDMA_FLAG = 20; + public static final int IST_INVALID_SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE_FLAG = 21; + public static final int MM_MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED_FLAG = 22; + private static final IntField MC_LEAP_SECOND_CORRECTION = IntField.range(23, 24); + public static final int VL_LOCAL_TIME_OFFSET_VALID_FLAG = 25; + public static final int LOCAL_TIME_OFFSET_SIGN = 26; + private static final IntField LOCAL_TIME_OFFSET_HOURS = IntField.range(27, 30); + public static final int LOCAL_TIME_OFFSET_HALF_HOUR = 31; + private static final IntField YEAR = IntField.range(32, 38); + private static final IntField MONTH = IntField.range(39, 42); + private static final IntField DAY = IntField.range(43, 47); + private static final IntField HOURS = IntField.range(48, 52); + private static final IntField MINUTES = IntField.range(53, 58); + private static final IntField MICRO_SLOTS = IntField.range(59, 71); + private static final DateFormat TIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z"); + private static final TimeZone NO_TIME_ZONE = new SimpleTimeZone(0, "NONE"); + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public SynchronizationBroadcast(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" SYSTEM TIME"); + if(isSystemTimeNotLockedToExternalReference()) + { + sb.append(" UNLOCKED"); + } + sb.append(":"); + TIME_FORMATTER.setTimeZone(getTimeZone()); + sb.append(" ").append(TIME_FORMATTER.format(new Date(getSystemTime()))); + sb.append(" LEAP-SECOND CORRECTION:").append(getLeapSecondCorrection()).append("mS"); + if(isMicroslotsLockedToMinuteRollover()) + { + sb.append(" MICROSLOT-MINUTE ROLLOVER:SLOW"); + } + else + { + sb.append(" MICROSLOT-MINUTE ROLLOVER:UNLOCKED"); + } + return sb.toString(); + } + + /** + * System Time (UTC) in milliseconds since java epoch + */ + public long getSystemTime() + { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + cal.clear(); + cal.set(Calendar.YEAR, getYear()); + cal.set(Calendar.MONTH, getMonth() - 1); + cal.set(Calendar.DAY_OF_MONTH, getDay()); + cal.set(Calendar.HOUR_OF_DAY, getHours()); + cal.set(Calendar.MINUTE, getMinutes()); + cal.set(Calendar.MILLISECOND, getMilliSeconds()); + return cal.getTimeInMillis(); + } + + /** + * System Time not locked to external time reference indicator + */ + public boolean isSystemTimeNotLockedToExternalReference() + { + return getMessage().get(IST_INVALID_SYSTEM_TIME_NOT_LOCKED_TO_EXTERNAL_REFERENCE_FLAG + getOffset()); + } + + /** + * Micro-slot-to-Minute rollover boundary lock indicator. + * + * Note: we invert the message value so that true indicates that the micro + * slots and minute rollovers are locked, whereas the ICD uses false to + * indicate a lock. + * + * @return false if the microslots and minutes rollover are not locked. This + * indicates that the microslots minute rollover is free rolling. + */ + public boolean isMicroslotsLockedToMinuteRollover() + { + return !getMessage().get(MM_MICRO_SLOTS_TO_MINUTE_ROLLOVER_UNLOCKED_FLAG + getOffset()); + } + + /** + * Leap second correction value that should be applied to the system time. + * + * The leap-second correction field indicates the number of 2.5 millisecond + * units that must be added to the system time to account for the insertion + * of leap seconds when the system time is represented as UTC universal time. + * + * @return leap-second correction value in milliseconds 0.0-10.0 + */ + public double getLeapSecondCorrection() + { + return (double)getInt(MC_LEAP_SECOND_CORRECTION) * 2.5d; + } + + + /** + * Indicates the local time offset fields contain valid information. + */ + public boolean isValidLocalTimeOffset() + { + return !getMessage().get(VL_LOCAL_TIME_OFFSET_VALID_FLAG + getOffset()); + } + + /** + * Local time zone offset from UTC universal time (zulu) when the VALID + * LOCAL TIME OFFSET flag indicates a valid offset. Otherwise, this method + * returns a static +00:00 indicating no local time offset. + */ + public TimeZone getTimeZone() + { + if(isValidLocalTimeOffset()) + { + int offset = 0; + + offset += getInt(LOCAL_TIME_OFFSET_HOURS) * 3600000; + offset += getMessage().get(LOCAL_TIME_OFFSET_HALF_HOUR + getOffset()) ? 1800000 : 0; + offset = getMessage().get(LOCAL_TIME_OFFSET_SIGN + getOffset()) ? -offset : offset; + return new SimpleTimeZone(offset, "LOCAL"); + } + else + { + return NO_TIME_ZONE; + } + } + + /** + * System time - year. + * + * @return year in range 2000-2127 + */ + public int getYear() + { + return 2000 + getInt(YEAR); + } + + /** + * System time - month + * + * @return month 1-12 where 1=January and 12=December + */ + public int getMonth() + { + return getInt(MONTH); + } + + /** + * System time - day of month + * + * @return day of month in range 1-31 + */ + public int getDay() + { + return getInt(DAY); + } + + /** + * System time - hours + * + * @return hours in range 0 - 23 + */ + public int getHours() + { + return getInt(HOURS); + } + + /** + * System time - minutes + * + * @return minutes in range 0 - 59 + */ + public int getMinutes() + { + return getInt(MINUTES); + } + + /** + * System time - milli-seconds + * + * Note: sub-millisecond values are rounded to nearest millisecond unit to + * conform to java Date internal milli-second precision level. + * + * @return milli-seconds in range 0 - 59999; + */ + public int getMilliSeconds() + { + return (int)((double)getMicroSlots() * 7.5); + } + + /** + * TDMA Micro Slots. + * + * Number of 7.5 millisecond micro slots since the last minute rollover, or + * since the last micro-slot counter rollover when the the micro-slot to + * minute rollover is unlocked. + * + * @return number of 7.5 mS micro-slots since last minute or micro-slot + * rollover + * + * This value supports mobile subscriber timing alignment with a TDMA traffic + * channel when a channel is granted from the control channels, so that when + * the mobile changes from the control channel to the traffic channel, it is + * already aligned with the traffic channel TDMA timing. + * + * Super frames occur every 360 ms (48 micro-slots) on FDMA and Ultra frames + * occur every 4 super frames on TDMA. + * + * Valid micro-slot values range 0-7999 and represent 60,000 + * milliseconds ( 8000 x 7.5 ms). + */ + public int getMicroSlots() + { + return getInt(MICRO_SLOTS); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SystemServiceBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SystemServiceBroadcast.java index 2d12364f1..8130c5132 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SystemServiceBroadcast.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/SystemServiceBroadcast.java @@ -1,32 +1,28 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.reference.Service; - import java.util.Collections; import java.util.List; @@ -35,13 +31,10 @@ */ public class SystemServiceBroadcast extends MacStructure { - private static final int[] TWUID_VALIDITY = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] AVAILABLE_SERVICES = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SUPPORTED_SERVICES = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, - 56, 57, 58, 59, 60, 61, 62, 63}; - private static final int[] REQUEST_PRIORITY_LEVEL = {64, 65, 66, 67, 68, 69, 70, 71}; - + private static final IntField TWUID_VALIDITY = IntField.length8(OCTET_2_BIT_8); + private static final IntField AVAILABLE_SERVICES = IntField.length24(OCTET_3_BIT_16); + private static final IntField SUPPORTED_SERVICES = IntField.length24(OCTET_6_BIT_40); + private static final IntField REQUEST_PRIORITY_LEVEL = IntField.length8(OCTET_9_BIT_64); private List mAvailableServices; private List mSupportedServices; @@ -65,14 +58,44 @@ public String toString() sb.append(getOpcode()); sb.append(" AVAILABLE SERVICES ").append(getAvailableServices()); sb.append(" SUPPORTED SERVICES ").append(getSupportedServices()); + sb.append(" ").append(getTemporaryWUIDValidity()); + sb.append(" MIN REQUEST PRI SUPPORTED:").append(getMinimumSupportedRequestPriorityLevel()); return sb.toString(); } + /** + * Indicates the lowest service request priority which is processed at the site at this time. This priority + * is reflected in the service options priority level specified in the (radio) request. + */ + public int getMinimumSupportedRequestPriorityLevel() + { + return getInt(REQUEST_PRIORITY_LEVEL); + } + + /** + * Registered roaming radio temporarily assigned WUID validity duration description. + */ + public String getTemporaryWUIDValidity() + { + int duration = getInt(TWUID_VALIDITY); + + if(duration > 0) + { + int hours = 4 + (duration / 2); + String minutes = (duration % 2 == 1) ? ":30" : ":00"; + return "ROAMING WUIDS VALID FOR:" + hours + minutes + " HOURS"; + } + else + { + return "ROAMING WUIDS VALID FOR: NO EXPIRY"; + } + } + public List getAvailableServices() { if(mAvailableServices == null) { - mAvailableServices = Service.getServices(getMessage().getInt(AVAILABLE_SERVICES, getOffset())); + mAvailableServices = Service.getServices(getInt(AVAILABLE_SERVICES)); } return mAvailableServices; @@ -82,7 +105,7 @@ public List getSupportedServices() { if(mSupportedServices == null) { - mSupportedServices = Service.getServices(getMessage().getInt(SUPPORTED_SERVICES, getOffset())); + mSupportedServices = Service.getServices(getInt(SUPPORTED_SERVICES)); } return mSupportedServices; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectAnswerRequest.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectAnswerRequest.java index d2bd0d747..337f74652 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectAnswerRequest.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectAnswerRequest.java @@ -1,34 +1,30 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.telephone.APCO25TelephoneNumber; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; import io.github.dsheirer.module.decode.p25.reference.Digit; - import java.util.ArrayList; import java.util.List; @@ -37,18 +33,17 @@ */ public class TelephoneInterconnectAnswerRequest extends MacStructure { - private static final int[] DIGIT_1 = {8, 9, 10, 11}; - private static final int[] DIGIT_2 = {12, 13, 14, 15}; - private static final int[] DIGIT_3 = {16, 17, 18, 19}; - private static final int[] DIGIT_4 = {20, 21, 22, 23}; - private static final int[] DIGIT_5 = {24, 25, 26, 27}; - private static final int[] DIGIT_6 = {28, 29, 30, 31}; - private static final int[] DIGIT_7 = {32, 33, 34, 35}; - private static final int[] DIGIT_8 = {36, 37, 38, 39}; - private static final int[] DIGIT_9 = {40, 41, 42, 43}; - private static final int[] DIGIT_10 = {44, 45, 46, 47}; - private static final int[] TARGET_ADDRESS = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68, 69, 70, 71}; + private static final IntField DIGIT_1 = IntField.length4(OCTET_2_BIT_8); + private static final IntField DIGIT_2 = IntField.length4(OCTET_2_BIT_8 + 4); + private static final IntField DIGIT_3 = IntField.length4(OCTET_3_BIT_16); + private static final IntField DIGIT_4 = IntField.length4(OCTET_3_BIT_16 + 4); + private static final IntField DIGIT_5 = IntField.length4(OCTET_4_BIT_24); + private static final IntField DIGIT_6 = IntField.length4(OCTET_4_BIT_24 + 4); + private static final IntField DIGIT_7 = IntField.length4(OCTET_5_BIT_32); + private static final IntField DIGIT_8 = IntField.length4(OCTET_5_BIT_32 + 4); + private static final IntField DIGIT_9 = IntField.length4(OCTET_6_BIT_40); + private static final IntField DIGIT_10 = IntField.length4(OCTET_6_BIT_40 + 4); + private static final IntField TARGET_ADDRESS = IntField.length4(OCTET_7_BIT_48); private List mIdentifiers; private Identifier mTargetAddress; @@ -84,7 +79,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -98,17 +93,16 @@ public Identifier getTelephoneNumber() if(mTelephoneNumber == null) { List digits = new ArrayList<>(); - digits.add(getMessage().getInt(DIGIT_1, getOffset())); - digits.add(getMessage().getInt(DIGIT_2, getOffset())); - digits.add(getMessage().getInt(DIGIT_3, getOffset())); - digits.add(getMessage().getInt(DIGIT_4, getOffset())); - digits.add(getMessage().getInt(DIGIT_5, getOffset())); - digits.add(getMessage().getInt(DIGIT_6, getOffset())); - digits.add(getMessage().getInt(DIGIT_7, getOffset())); - digits.add(getMessage().getInt(DIGIT_8, getOffset())); - digits.add(getMessage().getInt(DIGIT_9, getOffset())); - digits.add(getMessage().getInt(DIGIT_10, getOffset())); - + digits.add(getInt(DIGIT_1)); + digits.add(getInt(DIGIT_2)); + digits.add(getInt(DIGIT_3)); + digits.add(getInt(DIGIT_4)); + digits.add(getInt(DIGIT_5)); + digits.add(getInt(DIGIT_6)); + digits.add(getInt(DIGIT_7)); + digits.add(getInt(DIGIT_8)); + digits.add(getInt(DIGIT_9)); + digits.add(getInt(DIGIT_10)); mTelephoneNumber = APCO25TelephoneNumber.createAny(Digit.decode(digits)); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantExplicit.java new file mode 100644 index 000000000..443834fce --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantExplicit.java @@ -0,0 +1,153 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +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.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Telephone interconnect voice channel grant implicit + */ +public class TelephoneInterconnectVoiceChannelGrantExplicit extends MacStructureVoiceService + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider +{ + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_4_BIT_24); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_4_BIT_24 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_40); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_40 + 4); + private static final IntField CALL_TIMER = IntField.length16(OCTET_8_BIT_56); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_10_BIT_72); + + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public TelephoneInterconnectVoiceChannelGrantExplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO/FROM:").append(getTargetAddress()); + + if(hasCallTimer()) + { + sb.append(" TIMER:").append(getCallTimer() / 1000d).append("seconds"); + } + else + { + sb.append(" TIMER:none"); + } + + sb.append(" ").append(getServiceOptions()); + + return sb.toString(); + } + + /** + * Channel + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * Indicates if this message has a non-zero call timer value. + */ + public boolean hasCallTimer() + { + return hasInt(CALL_TIMER); + } + + /** + * Call timer in milliseconds. + * + * @return timer in milliseconds where a value of 0 indicates no timer. + */ + public long getCallTimer() + { + return getInt(CALL_TIMER) * 100; //milliseconds + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * Implements the channel grant details provider interface but always returns null. + */ + @Override + public Identifier getSourceAddress() + { + return null; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantImplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantImplicit.java new file mode 100644 index 000000000..8010a7d15 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantImplicit.java @@ -0,0 +1,152 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Telephone interconnect voice channel grant implicit + */ +public class TelephoneInterconnectVoiceChannelGrantImplicit extends MacStructureVoiceService + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider +{ + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_4_BIT_24); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_4_BIT_24 + 4); + private static final IntField CALL_TIMER = IntField.length16(OCTET_6_BIT_40); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_8_BIT_56); + + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public TelephoneInterconnectVoiceChannelGrantImplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO/FROM:").append(getTargetAddress()); + + if(hasCallTimer()) + { + sb.append(" TIMER:").append(getCallTimer() / 1000d).append("seconds"); + } + else + { + sb.append(" TIMER:none"); + } + + sb.append(" ").append(getServiceOptions()); + + return sb.toString(); + } + + /** + * Channel + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * Indicates if this message has a non-zero call timer value. + */ + public boolean hasCallTimer() + { + return hasInt(CALL_TIMER); + } + + /** + * Call timer in milliseconds. + * + * @return timer in milliseconds where a value of 0 indicates no timer. + */ + public long getCallTimer() + { + return getInt(CALL_TIMER) * 100; //milliseconds + } + + /** + * Implements the channel grant detail provider interface, but always returns null. + */ + @Override + public Identifier getSourceAddress() + { + return null; + } + + /** + * Target address + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantUpdateExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantUpdateExplicit.java new file mode 100644 index 000000000..da5ed754a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantUpdateExplicit.java @@ -0,0 +1,133 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +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.identifier.radio.APCO25RadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Telephone interconnect voice channel grant update implicit + */ +public class TelephoneInterconnectVoiceChannelGrantUpdateExplicit extends MacStructureVoiceService +{ + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_4_BIT_24); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_4_BIT_24 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_40); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_40 + 4); + private static final IntField CALL_TIMER = IntField.length16(OCTET_8_BIT_56); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_10_BIT_72); + + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public TelephoneInterconnectVoiceChannelGrantUpdateExplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO/FROM:").append(getTargetAddress()); + + if(hasCallTimer()) + { + sb.append(" TIMER:").append(getCallTimer() / 1000d).append("seconds"); + } + else + { + sb.append(" TIMER:none"); + } + + sb.append(" ").append(getServiceOptions()); + + return sb.toString(); + } + + /** + * Channel + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * Indicates if this message has a non-zero call timer value. + */ + public boolean hasCallTimer() + { + return hasInt(CALL_TIMER); + } + + /** + * Call timer in milliseconds. + * + * @return timer in milliseconds where a value of 0 indicates no timer. + */ + public long getCallTimer() + { + return getInt(CALL_TIMER) * 100; //milliseconds + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantUpdateImplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantUpdateImplicit.java new file mode 100644 index 000000000..70abab483 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelGrantUpdateImplicit.java @@ -0,0 +1,129 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Telephone interconnect voice channel grant update implicit + */ +public class TelephoneInterconnectVoiceChannelGrantUpdateImplicit extends MacStructureVoiceService +{ + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_4_BIT_24); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_4_BIT_24 + 4); + private static final IntField CALL_TIMER = IntField.length16(OCTET_6_BIT_40); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_8_BIT_56); + + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public TelephoneInterconnectVoiceChannelGrantUpdateImplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO/FROM:").append(getTargetAddress()); + + if(hasCallTimer()) + { + sb.append(" TIMER:").append(getCallTimer() / 1000d).append("seconds"); + } + else + { + sb.append(" TIMER:none"); + } + + sb.append(" ").append(getServiceOptions()); + + return sb.toString(); + } + + /** + * Channel + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * Indicates if this message has a non-zero call timer value. + */ + public boolean hasCallTimer() + { + return hasInt(CALL_TIMER); + } + + /** + * Call timer in milliseconds. + * + * @return timer in milliseconds where a value of 0 indicates no timer. + */ + public long getCallTimer() + { + return getInt(CALL_TIMER) * 100; //milliseconds + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelUser.java index 18083ac2d..96f8f355d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TelephoneInterconnectVoiceChannelUser.java @@ -1,49 +1,40 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** * Telephone interconnect voice channel user */ -public class TelephoneInterconnectVoiceChannelUser extends MacStructure +public class TelephoneInterconnectVoiceChannelUser extends MacStructureVoiceService { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] CALL_TIMER = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - + private static final IntField CALL_TIMER = IntField.range(16, 31); + private static final IntField TARGET_ADDRESS = IntField.range(32, 55); private List mIdentifiers; - private Identifier mToOrFromAddress; - private VoiceServiceOptions mServiceOptions; + private Identifier mTargetAddress; /** * Constructs the message @@ -63,17 +54,15 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getOpcode()); - sb.append(" TO/FROM:").append(getToOrFromAddress()); + sb.append(" TO/FROM:").append(getTargetAddress()); - long timer = getCallTimer(); - - if(timer == 0) + if(hasCallTimer()) { - sb.append(" TIMER:none"); + sb.append(" TIMER:").append(getCallTimer() / 1000d).append("seconds"); } else { - sb.append(" TIMER:").append(timer / 1000d).append("seconds"); + sb.append(" TIMER:none"); } sb.append(" ").append(getServiceOptions()); @@ -82,39 +71,34 @@ public String toString() } /** - * Voice channel service options + * Call timer in milliseconds. + * + * @return timer in milliseconds where a value of 0 indicates no timer. */ - public VoiceServiceOptions getServiceOptions() + public long getCallTimer() { - if(mServiceOptions == null) - { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; + return getInt(CALL_TIMER) * 100; //milliseconds } /** - * Call timer in milliseconds. - * - * @return timer in milliseconds where a value of 0 indicates no timer. + * Indicates if this message has a non-zero call timer value. */ - public long getCallTimer() + public boolean hasCallTimer() { - return getMessage().getInt(CALL_TIMER, getOffset()) * 100; //milliseconds + return hasInt(CALL_TIMER); } /** - * From Radio Unit + * The Radio Unit talking to a landline. The role can be either TO or FROM, but we use the TO role for consistency. */ - public Identifier getToOrFromAddress() + public Identifier getTargetAddress() { - if(mToOrFromAddress == null) + if(mTargetAddress == null) { - mToOrFromAddress = APCO25RadioIdentifier.createAny(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createFrom(getInt(TARGET_ADDRESS)); } - return mToOrFromAddress; + return mTargetAddress; } @Override @@ -123,7 +107,7 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getToOrFromAddress()); + mIdentifiers.add(getTargetAddress()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TimeAndDateAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TimeAndDateAnnouncement.java new file mode 100644 index 000000000..c1e6a356b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/TimeAndDateAnnouncement.java @@ -0,0 +1,95 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.List; + +/** + * Time and date announcement + */ +public class TimeAndDateAnnouncement extends MacStructure +{ + private static final int VD_FLAG = 8; + private static final int VT_FLAG = 9; + private static final int VL_FLAG = 10; + private static final int LOCAL_TIME_OFFSET_SIGN = 11; + private static final IntField LOCAL_TIME_OFFSET = IntField.range(12, 23); + private static final IntField MONTH = IntField.range(24, 27); + private static final IntField DAY = IntField.range(28, 32); + private static final IntField YEAR = IntField.range(33, 45); + private static final IntField HOURS = IntField.range(48, 52); + private static final IntField MINUTES = IntField.range(53, 58); + private static final IntField SECONDS = IntField.range(59, 64); + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public TimeAndDateAnnouncement(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" ").append(getDateAndTime()); + return sb.toString(); + } + + /** + * Date and time value + * @return date in milliseconds since epoch (1970). + */ + public OffsetDateTime getDateAndTime() + { + boolean hasDate = getMessage().get(VD_FLAG + getOffset()); + boolean hasTime = getMessage().get(VT_FLAG + getOffset()); + boolean hasOffset = getMessage().get(VL_FLAG + getOffset()); + int year = hasDate ? getInt(YEAR) : 0; + int month = hasDate ? getInt(MONTH) : 0; + int day = hasDate ? getInt(DAY) : 0; + int hours = hasTime ? getInt(HOURS) : 0; + int minutes = hasTime ? getInt(MINUTES) : 0; + int seconds = hasTime ? getInt(SECONDS) : 0; + int offsetMinutes = hasOffset ? getInt(LOCAL_TIME_OFFSET) : 0; + offsetMinutes *= getMessage().get(LOCAL_TIME_OFFSET_SIGN + getOffset()) ? -1 : 1; + return OffsetDateTime.of(year, month, day, hours, minutes, seconds, 0, + ZoneOffset.ofHoursMinutes(0, offsetMinutes)); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitDeRegistrationAcknowledge.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitDeRegistrationAcknowledge.java new file mode 100644 index 000000000..7e8eb6bf0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitDeRegistrationAcknowledge.java @@ -0,0 +1,87 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import java.util.ArrayList; +import java.util.List; + +/** + * Unit de-registration acknowledge + */ +public class UnitDeRegistrationAcknowledge extends MacStructure +{ + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_3_BIT_16); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_7_BIT_48); + private APCO25FullyQualifiedRadioIdentifier mSourceSUID; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public UnitDeRegistrationAcknowledge(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getSourceSUID()); + + return sb.toString(); + } + + public Identifier getSourceSUID() + { + if(mSourceSUID == null) + { + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSourceSUID = APCO25FullyQualifiedRadioIdentifier.createTo(id, wacn, system, id); + } + + return mSourceSUID; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceSUID()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationCommandAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationCommandAbbreviated.java index 0aa33c848..b736be93b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationCommandAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationCommandAbbreviated.java @@ -1,45 +1,38 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; import java.util.List; /** - * Unit registration command - abbreviated format + * Unit registration command abbreviated */ public class UnitRegistrationCommandAbbreviated extends MacStructure { - private static final int[] TARGET_ADDRESS = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31}; - private static final int[] SOURCE_ADDRESS = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55}; - + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_2_BIT_8); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_5_BIT_32); private List mIdentifiers; private Identifier mTargetAddress; private Identifier mSourceAddress; @@ -74,7 +67,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -87,7 +80,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationResponseAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationResponseAbbreviated.java new file mode 100644 index 000000000..5c8c69be9 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationResponseAbbreviated.java @@ -0,0 +1,96 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.reference.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * Unit registration response abbreviated + */ +public class UnitRegistrationResponseAbbreviated extends MacStructure +{ + private static final IntField RESPONSE = IntField.range(18, 19); + private static final IntField SYSTEM = IntField.length12(OCTET_3_BIT_16 + 4); + private static final IntField SOURCE_ID = IntField.length24(OCTET_5_BIT_32); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_8_BIT_56); + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public UnitRegistrationResponseAbbreviated(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" REGISTRATION ").append(getResponse()); + + return sb.toString(); + } + + public Response getResponse() + { + return Response.fromValue(getInt(RESPONSE)); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + int address = getInt(SOURCE_ADDRESS); + int wacn = 0; //wacn is not included here + int system = getInt(SYSTEM); + int id = getInt(SOURCE_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationResponseExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationResponseExtended.java new file mode 100644 index 000000000..b75d701f5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitRegistrationResponseExtended.java @@ -0,0 +1,97 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.reference.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * Unit registration response extended + */ +public class UnitRegistrationResponseExtended extends MacStructure +{ + private static final IntField RESPONSE = IntField.range(22, 23); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_4_BIT_24); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_6_BIT_40 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_8_BIT_56); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_11_BIT_80); + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public UnitRegistrationResponseExtended(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" REGISTRATION ").append(getResponse()); + + return sb.toString(); + } + + public Response getResponse() + { + return Response.fromValue(getInt(RESPONSE)); + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + int address = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitAnswerRequestAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitAnswerRequestAbbreviated.java index d52a0e292..4840788b5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitAnswerRequestAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitAnswerRequestAbbreviated.java @@ -1,51 +1,42 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** * Unit-to-unit answer request - abbreviated format */ -public class UnitToUnitAnswerRequestAbbreviated extends MacStructure +public class UnitToUnitAnswerRequestAbbreviated extends MacStructureVoiceService { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] TARGET_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 38, 39}; - private static final int[] SOURCE_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63}; + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_16); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_40); private List mIdentifiers; private Identifier mTargetAddress; private Identifier mSourceAddress; - private VoiceServiceOptions mServiceOptions; /** * Constructs the message @@ -72,26 +63,13 @@ public String toString() } /** - * Voice channel service options - */ - public VoiceServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - - /** - * To Talkgroup + * To Radio */ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -104,7 +82,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitAnswerRequestExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitAnswerRequestExtended.java index b721be037..c141e2356 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitAnswerRequestExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitAnswerRequestExtended.java @@ -1,55 +1,45 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** - * Unit-to-Unit answer request - extended format + * Unit-to-Unit answer request extended */ -public class UnitToUnitAnswerRequestExtended extends MacStructure +public class UnitToUnitAnswerRequestExtended extends MacStructureVoiceService { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] TARGET_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39}; - private static final int[] FULLY_QUALIFIED_SOURCE_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[] FULLY_QUALIFIED_SOURCE_SYSTEM = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] FULLY_QUALIFIED_SOURCE_ID = {72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, - 87, 88, 89, 90, 91, 92, 93, 94, 95}; + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_16); + private static final IntField SOURCE_SUID_WACN = IntField.range(OCTET_6_BIT_40, OCTET_6_BIT_40 + 20); + private static final IntField SOURCE_SUID_SYSTEM = IntField.range(60, 71); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_10_BIT_72); private List mIdentifiers; - private VoiceServiceOptions mServiceOptions; private Identifier mTargetAddress; - private Identifier mSourceSuid; + private APCO25FullyQualifiedRadioIdentifier mSourceSUID; /** * Constructs the message @@ -69,25 +59,12 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getOpcode()); - sb.append(" FM:").append(getSourceSuid()); + sb.append(" FM:").append(getSourceSUID()); sb.append(" TO:").append(getTargetAddress()); sb.append(" ").append(getServiceOptions()); return sb.toString(); } - /** - * Voice channel service options - */ - public VoiceServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - /** * To Talkgroup */ @@ -95,24 +72,24 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; } - public Identifier getSourceSuid() + public APCO25FullyQualifiedRadioIdentifier getSourceSUID() { - if(mSourceSuid == null) + if(mSourceSUID == null) { - int wacn = getMessage().getInt(FULLY_QUALIFIED_SOURCE_WACN, getOffset()); - int system = getMessage().getInt(FULLY_QUALIFIED_SOURCE_SYSTEM, getOffset()); - int id = getMessage().getInt(FULLY_QUALIFIED_SOURCE_ID, getOffset()); - - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(wacn, system, id); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + //Fully qualified, but not aliased - reuse the ID as the persona. + mSourceSUID = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); } - return mSourceSuid; + return mSourceSUID; } @Override @@ -122,7 +99,7 @@ public List getIdentifiers() { mIdentifiers = new ArrayList<>(); mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceSuid()); + mIdentifiers.add(getSourceSUID()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantExtended.java deleted file mode 100644 index fed755b02..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantExtended.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -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.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; -import java.util.List; - -/** - * Unit-to-Unit voice channel grant - extended format - */ -public class UnitToUnitVoiceChannelGrantExtended extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] TRANSMIT_FREQUENCY_BAND = {8, 9, 10, 11}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] RECEIVE_FREQUENCY_BAND = {24, 25, 26, 27}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] FULLY_QUALIFIED_SOURCE_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[] FULLY_QUALIFIED_SOURCE_SYSTEM = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] FULLY_QUALIFIED_SOURCE_ID = {72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, - 87, 88, 89, 90, 91, 92, 93, 94, 95}; - private static final int[] TARGET_ADDRESS = {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, - 111, 112, 113, 114, 115, 116, 117, 118, 119}; - - private APCO25Channel mChannel; - private List mIdentifiers; - private Identifier mTargetAddress; - private Identifier mSourceSuid; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public UnitToUnitVoiceChannelGrantExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceSuid()); - sb.append(" TO:").append(getTargetAddress()); - sb.append(" CHAN:").append(getChannel()); - return sb.toString(); - } - - public APCO25Channel getChannel() - { - if(mChannel == null) - { - mChannel = APCO25ExplicitChannel.create(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset())); - } - - return mChannel; - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - public Identifier getSourceSuid() - { - if(mSourceSuid == null) - { - int wacn = getMessage().getInt(FULLY_QUALIFIED_SOURCE_WACN, getOffset()); - int system = getMessage().getInt(FULLY_QUALIFIED_SOURCE_SYSTEM, getOffset()); - int id = getMessage().getInt(FULLY_QUALIFIED_SOURCE_ID, getOffset()); - - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(wacn, system, id); - } - - return mSourceSuid; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceSuid()); - mIdentifiers.add(getChannel()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - List descriptors = new ArrayList<>(); - descriptors.add(getChannel()); - return descriptors; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateAbbreviated.java index 279ad12bd..6cab382ce 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateAbbreviated.java @@ -1,49 +1,44 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * Unit-to-unit voice channel grant update - abbreviated format + * Unit-to-unit voice channel grant update abbreviated */ public class UnitToUnitVoiceChannelGrantUpdateAbbreviated extends MacStructure implements IFrequencyBandReceiver { - private static final int[] FREQUENCY_BAND = {8, 9, 10, 11}; - private static final int[] CHANNEL_NUMBER = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] TARGET_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 38, 39}; - private static final int[] SOURCE_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63}; + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_2_BIT_8); + private static final IntField CHANNEL_NUMBER = IntField.range(12, 23); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_4_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_7_BIT_48); private List mIdentifiers; private APCO25Channel mChannel; @@ -81,8 +76,7 @@ public APCO25Channel getChannel() { if(mChannel == null) { - mChannel = APCO25Channel.create(getMessage().getInt(FREQUENCY_BAND, getOffset()), - getMessage().getInt(CHANNEL_NUMBER, getOffset())); + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); } return mChannel; @@ -95,7 +89,7 @@ public Identifier getTargetAddress() { if(mTargetAddress == null) { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); } return mTargetAddress; @@ -108,7 +102,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; @@ -131,8 +125,6 @@ public List getIdentifiers() @Override public List getChannels() { - List channels = new ArrayList<>(); - channels.add(getChannel()); - return channels; + return Collections.singletonList(getChannel()); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateExtended.java deleted file mode 100644 index ea7aa86ec..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateExtended.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.channel.IChannelDescriptor; -import io.github.dsheirer.identifier.Identifier; -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.identifier.channel.P25P2ExplicitChannel; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.ArrayList; -import java.util.List; - -/** - * Unit-to-Unit voice channel grant update - extended format - */ -public class UnitToUnitVoiceChannelGrantUpdateExtended extends MacStructure implements IFrequencyBandReceiver -{ - private static final int[] TRANSMIT_FREQUENCY_BAND = {8, 9, 10, 11}; - private static final int[] TRANSMIT_CHANNEL_NUMBER = {12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] RECEIVE_FREQUENCY_BAND = {24, 25, 26, 27}; - private static final int[] RECEIVE_CHANNEL_NUMBER = {28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; - private static final int[] FULLY_QUALIFIED_SOURCE_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[] FULLY_QUALIFIED_SOURCE_SYSTEM = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] FULLY_QUALIFIED_SOURCE_ID = {72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, - 87, 88, 89, 90, 91, 92, 93, 94, 95}; - private static final int[] TARGET_ADDRESS = {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, - 111, 112, 113, 114, 115, 116, 117, 118, 119}; - - private APCO25Channel mChannel; - private List mIdentifiers; - private Identifier mTargetAddress; - private Identifier mSourceSuid; - - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset into the message for this structure - */ - public UnitToUnitVoiceChannelGrantUpdateExtended(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getOpcode()); - sb.append(" FM:").append(getSourceSuid()); - sb.append(" TO:").append(getTargetAddress()); - sb.append(" CHAN:").append(getChannel()); - return sb.toString(); - } - - public APCO25Channel getChannel() - { - if(mChannel == null) - { - mChannel = new APCO25ExplicitChannel(new P25P2ExplicitChannel(getMessage().getInt(TRANSMIT_FREQUENCY_BAND, getOffset()), - getMessage().getInt(TRANSMIT_CHANNEL_NUMBER, getOffset()), - getMessage().getInt(RECEIVE_FREQUENCY_BAND, getOffset()), - getMessage().getInt(RECEIVE_CHANNEL_NUMBER, getOffset()))); - } - - return mChannel; - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - public Identifier getSourceSuid() - { - if(mSourceSuid == null) - { - int wacn = getMessage().getInt(FULLY_QUALIFIED_SOURCE_WACN, getOffset()); - int system = getMessage().getInt(FULLY_QUALIFIED_SOURCE_SYSTEM, getOffset()); - int id = getMessage().getInt(FULLY_QUALIFIED_SOURCE_ID, getOffset()); - - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(wacn, system, id); - } - - return mSourceSuid; - } - - @Override - public List getIdentifiers() - { - if(mIdentifiers == null) - { - mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceSuid()); - mIdentifiers.add(getChannel()); - } - - return mIdentifiers; - } - - @Override - public List getChannels() - { - List descriptors = new ArrayList<>(); - descriptors.add(getChannel()); - return descriptors; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateExtendedLCCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateExtendedLCCH.java new file mode 100644 index 000000000..0bcbb68c8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateExtendedLCCH.java @@ -0,0 +1,169 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +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.identifier.channel.P25P2ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Unit-to-Unit voice channel grant update extended LCCH + */ +public class UnitToUnitVoiceChannelGrantUpdateExtendedLCCH extends MacStructureMultiFragment + implements IFrequencyBandReceiver, IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_4_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_5_BIT_32); + private static final IntField SOURCE_SUID_WACN = IntField.range(OCTET_8_BIT_56, OCTET_8_BIT_56 + 20); + private static final IntField SOURCE_SUID_SYSTEM = IntField.range(76, 87); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_12_BIT_88); + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_15_BIT_112); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.range(116, 127); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_17_BIT_128); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.range(128, 139); + + private static final IntField FRAGMENT_0_TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_16); + private static final IntField FRAGMENT_0_TARGET_SUID_WACN = IntField.range(OCTET_8_BIT_56, OCTET_8_BIT_56 + 20); + private static final IntField FRAGMENT_0_TARGET_SUID_SYSTEM = IntField.range(76, 87); + private static final IntField FRAGMENT_0_TARGET_SUID_ID = IntField.length24(OCTET_12_BIT_88); + + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + private VoiceServiceOptions mServiceOptions; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public UnitToUnitVoiceChannelGrantUpdateExtendedLCCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceAddress()); + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + sb.append(" CHAN:").append(getChannel()); + sb.append(" ").append(getServiceOptions()); + return sb.toString(); + } + + public VoiceServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = new APCO25ExplicitChannel(new P25P2ExplicitChannel(getInt(TRANSMIT_FREQUENCY_BAND), + getInt(TRANSMIT_CHANNEL_NUMBER), getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER))); + } + + return mChannel; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null && hasFragment(0)) + { + int address = getFragment(0).getInt(FRAGMENT_0_TARGET_ADDRESS); + int wacn = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_WACN); + int system = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_SYSTEM); + int id = getFragment(0).getInt(FRAGMENT_0_TARGET_SUID_ID); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createTo(address, wacn, system, id); + } + + return mTargetAddress; + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + int address = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + //Note: this has to be dynamically constructed each time to account for late-add continuation fragments. + List identifiers = new ArrayList<>(); + + if(getTargetAddress() != null) + { + identifiers.add(getTargetAddress()); + } + + if(getSourceAddress() != null) + { + identifiers.add(getSourceAddress()); + } + + identifiers.add(getChannel()); + + return identifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateExtendedVCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateExtendedVCH.java new file mode 100644 index 000000000..6cd1929f7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelGrantUpdateExtendedVCH.java @@ -0,0 +1,136 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +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.identifier.channel.P25P2ExplicitChannel; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Unit-to-Unit voice channel grant update extended VCH + */ +public class UnitToUnitVoiceChannelGrantUpdateExtendedVCH extends MacStructure implements IFrequencyBandReceiver +{ + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_2_BIT_8); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.range(12, 23); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_4_BIT_24); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.range(28, 39); + private static final IntField SOURCE_SUID_WACN = IntField.range(OCTET_6_BIT_40, OCTET_6_BIT_40 + 20); + private static final IntField SOURCE_SUID_SYSTEM = IntField.range(60, 71); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_10_BIT_72); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_13_BIT_96); + + private APCO25Channel mChannel; + private List mIdentifiers; + private Identifier mTargetAddress; + private Identifier mSourceSuid; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public UnitToUnitVoiceChannelGrantUpdateExtendedVCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceSuid()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" CHAN:").append(getChannel()); + return sb.toString(); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = new APCO25ExplicitChannel(new P25P2ExplicitChannel(getInt(TRANSMIT_FREQUENCY_BAND), + getInt(TRANSMIT_CHANNEL_NUMBER), getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER))); + } + + return mChannel; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public Identifier getSourceSuid() + { + if(mSourceSuid == null) + { + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + //Fully qualified, but not aliased - reuse the ID as the persona. + mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceSuid; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceSuid()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelUserAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelUserAbbreviated.java index 426541c41..fa1e277b0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelUserAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelUserAbbreviated.java @@ -1,51 +1,40 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** * Unit-to-unit voice channel user - abbreviated format */ -public class UnitToUnitVoiceChannelUserAbbreviated extends MacStructure +public class UnitToUnitVoiceChannelUserAbbreviated extends MacStructureUnitVoiceService { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] TARGET_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 35, 36, 37, 38, 39}; - private static final int[] SOURCE_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63}; + private static final IntField SOURCE_ADDRESS = IntField.range(40, 63); private List mIdentifiers; - private Identifier mTargetAddress; private Identifier mSourceAddress; - private VoiceServiceOptions mServiceOptions; /** * Constructs the message @@ -71,32 +60,6 @@ public String toString() return sb.toString(); } - /** - * Voice channel service options - */ - public VoiceServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - /** * From Radio Unit */ @@ -104,7 +67,7 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); } return mSourceAddress; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelUserExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelUserExtended.java index 26ffb25b8..ba994f536 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelUserExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceChannelUserExtended.java @@ -1,58 +1,43 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; -import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; /** - * Unit-to-Unit voice channel user - extended format + * Unit-to-Unit voice channel user - extended */ -public class UnitToUnitVoiceChannelUserExtended extends MacStructure +public class UnitToUnitVoiceChannelUserExtended extends MacStructureUnitVoiceService { - private static final int[] SERVICE_OPTIONS = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] TARGET_ADDRESS = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39}; - private static final int[] SOURCE_ADDRESS = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63}; - private static final int[] FULLY_QUALIFIED_SOURCE_WACN = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, - 78, 79, 80, 81, 82, 83}; - private static final int[] FULLY_QUALIFIED_SOURCE_SYSTEM = {84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}; - private static final int[] FULLY_QUALIFIED_SOURCE_ID = {96, 97, 98, 99, 100, 101, - 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119}; + private static final IntField SOURCE_ADDRESS = IntField.range(40, 63); + private static final IntField SOURCE_SUID_WACN = IntField.range(64, 83); + private static final IntField SOURCE_SUID_SYSTEM = IntField.range(84, 95); + private static final IntField SOURCE_SUID_ID = IntField.range(96, 119); private List mIdentifiers; - private VoiceServiceOptions mServiceOptions; - private Identifier mTargetAddress; - private Identifier mSourceAddress; - private APCO25FullyQualifiedRadioIdentifier mSourceSuid; + private APCO25FullyQualifiedRadioIdentifier mSource; /** * Constructs the message @@ -72,64 +57,27 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getOpcode()); - sb.append(" FM:").append(getSourceAddress()); + sb.append(" FM:").append(getSource()); sb.append(" TO:").append(getTargetAddress()); - sb.append(" SUID:").append(getSourceSuid()); sb.append(" ").append(getServiceOptions()); return sb.toString(); } /** - * Voice channel service options - */ - public VoiceServiceOptions getServiceOptions() - { - if(mServiceOptions == null) - { - mServiceOptions = new VoiceServiceOptions(getMessage().getInt(SERVICE_OPTIONS, getOffset())); - } - - return mServiceOptions; - } - - /** - * To Talkgroup - */ - public Identifier getTargetAddress() - { - if(mTargetAddress == null) - { - mTargetAddress = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS, getOffset())); - } - - return mTargetAddress; - } - - /** - * From Radio Unit + * From Radio Unit using a fully qualified radio identifier and a local address (persona). */ - public Identifier getSourceAddress() - { - if(mSourceAddress == null) - { - mSourceAddress = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_ADDRESS, getOffset())); - } - - return mSourceAddress; - } - - public APCO25FullyQualifiedRadioIdentifier getSourceSuid() + public APCO25FullyQualifiedRadioIdentifier getSource() { - if(mSourceSuid == null) + if(mSource == null) { - int wacn = getMessage().getInt(FULLY_QUALIFIED_SOURCE_WACN, getOffset()); - int system = getMessage().getInt(FULLY_QUALIFIED_SOURCE_SYSTEM, getOffset()); - int id = getMessage().getInt(FULLY_QUALIFIED_SOURCE_ID, getOffset()); - - mSourceSuid = APCO25FullyQualifiedRadioIdentifier.createFrom(wacn, system, id); + int address = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSource = APCO25FullyQualifiedRadioIdentifier.createFrom(address, wacn, system, id); } - return mSourceSuid; + return mSource; } @Override @@ -139,8 +87,7 @@ public List getIdentifiers() { mIdentifiers = new ArrayList<>(); mIdentifiers.add(getTargetAddress()); - mIdentifiers.add(getSourceAddress()); - mIdentifiers.add(getSourceSuid()); + mIdentifiers.add(getSource()); } return mIdentifiers; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceServiceChannelGrantAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceServiceChannelGrantAbbreviated.java new file mode 100644 index 000000000..fc12b5c83 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceServiceChannelGrantAbbreviated.java @@ -0,0 +1,149 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Unit-to-unit voice service channel grant abbreviated + */ +public class UnitToUnitVoiceServiceChannelGrantAbbreviated extends MacStructure + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider, IServiceOptionsProvider +{ + private static final IntField FREQUENCY_BAND = IntField.range(8, 11); + private static final IntField CHANNEL_NUMBER = IntField.range(12, 23); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_4_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_7_BIT_48); + + private VoiceServiceOptions mServiceOptions; + private List mIdentifiers; + private APCO25Channel mChannel; + private Identifier mTargetAddress; + private Identifier mSourceAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public UnitToUnitVoiceServiceChannelGrantAbbreviated(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" CHAN:").append(getChannel()); + return sb.toString(); + } + + /** + * Implements the channel grant detail provider but always returns an empty service options. + */ + public ServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(0); + } + + return mServiceOptions; + } + + /** + * Channel + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + /** + * From Radio Unit + */ + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceServiceChannelGrantExtendedLCCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceServiceChannelGrantExtendedLCCH.java new file mode 100644 index 000000000..e5d973d0e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceServiceChannelGrantExtendedLCCH.java @@ -0,0 +1,175 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +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.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Unit-to-Unit voice channel grant - extended LCCH + */ +public class UnitToUnitVoiceServiceChannelGrantExtendedLCCH extends MacStructureMultiFragment + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider, IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_4_BIT_24); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_5_BIT_32); + private static final IntField SOURCE_SUID_WACN = IntField.range(OCTET_8_BIT_56, OCTET_8_BIT_56 + 20); + private static final IntField SOURCE_SUID_SYSTEM = IntField.range(76, 87); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_12_BIT_88); + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.range(OCTET_15_BIT_112, OCTET_15_BIT_112 + 4); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.range(116, 127); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.range(OCTET_17_BIT_128, OCTET_17_BIT_128 + 4); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.range(132, 143); + private static final IntField FRAGMENT_0_TARGET_ADDRESS = IntField.length24(OCTET_3_BIT_16); + private static final IntField FRAGMENT_0_FULLY_QUALIFIED_TARGET_WACN = IntField.range(OCTET_6_BIT_40, OCTET_6_BIT_40 + 20); + private static final IntField FRAGMENT_0_FULLY_QUALIFIED_TARGET_SYSTEM = IntField.range(60, 71); + private static final IntField FRAGMENT_0_FULLY_QUALIFIED_TARGET_RADIO = IntField.length24(OCTET_10_BIT_72); + private static final IntField FRAGMENT_0_MULTI_FRAGMENT_CRC = IntField.range(96, 111); + + private ServiceOptions mServiceOptions; + private APCO25Channel mChannel; + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + private APCO25FullyQualifiedRadioIdentifier mTargetAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public UnitToUnitVoiceServiceChannelGrantExtendedLCCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("UNIT-TO-UNIT VOICE SERVICE CHANNEL GRANT EXTENDED (1/2)"); + sb.append(" FM:").append(getSourceAddress()); + + if(getTargetAddress() != null) + { + sb.append(" TO:").append(getTargetAddress()); + } + sb.append(" CHAN:").append(getChannel()); + return sb.toString(); + } + + /** + * Service options for the voice call. + */ + public ServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + /** + * Fully qualified radio source identify with aliased with a local persona radio address. + */ + public APCO25FullyQualifiedRadioIdentifier getSourceAddress() + { + if(mSourceAddress == null) + { + int localAddress = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int radio = getInt(SOURCE_SUID_ID); + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, radio); + } + + return mSourceAddress; + } + + /** + * Fully qualified radio source identify with aliased with a local persona radio address. + */ + public APCO25FullyQualifiedRadioIdentifier getTargetAddress() + { + if(mTargetAddress == null && hasFragment(0)) + { + int localAddress = getFragment(0).getInt(FRAGMENT_0_TARGET_ADDRESS); + int wacn = getFragment(0).getInt(FRAGMENT_0_FULLY_QUALIFIED_TARGET_WACN); + int system = getFragment(0).getInt(FRAGMENT_0_FULLY_QUALIFIED_TARGET_SYSTEM); + int radio = getFragment(0).getInt(FRAGMENT_0_FULLY_QUALIFIED_TARGET_RADIO); + mTargetAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, radio); + } + + return mTargetAddress; + } + + /** + * Channel with explicit transmit and receive frequency bands. + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); + } + + return mChannel; + } + + @Override + public List getIdentifiers() + { + //Note: this has to be dynamically constructed each time to account for late-add continuation fragments. + List identifiers = new ArrayList<>(); + identifiers.add(getSourceAddress()); + + if(getTargetAddress() != null) + { + identifiers.add(getTargetAddress()); + } + + identifiers.add(getChannel()); + + return identifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceServiceChannelGrantExtendedVCH.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceServiceChannelGrantExtendedVCH.java new file mode 100644 index 000000000..15b4663ed --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnitToUnitVoiceServiceChannelGrantExtendedVCH.java @@ -0,0 +1,153 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +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.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Unit-to-Unit voice service channel grant extended VCH + */ +public class UnitToUnitVoiceServiceChannelGrantExtendedVCH extends MacStructure + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider, IServiceOptionsProvider +{ + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.range(8, 11); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.range(12, 23); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.range(24, 27); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.range(28, 39); + private static final IntField SOURCE_SUID_WACN = IntField.range(40, 59); + private static final IntField SOURCE_SUID_SYSTEM = IntField.range(60, 71); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_10_BIT_72); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_13_BIT_96); + + private VoiceServiceOptions mServiceOptions; + private APCO25Channel mChannel; + private List mIdentifiers; + private Identifier mTargetAddress; + private APCO25FullyQualifiedRadioIdentifier mSourceAddress; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public UnitToUnitVoiceServiceChannelGrantExtendedVCH(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" CHAN:").append(getChannel()); + return sb.toString(); + } + + /** + * Implements the channel grant detail provider interface but always returns an empty service options. + */ + public VoiceServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(0); + } + + return mServiceOptions; + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + public APCO25FullyQualifiedRadioIdentifier getSourceAddress() + { + if(mSourceAddress == null) + { + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + //Fully qualified, but not aliased - reuse the ID as the persona. + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id); + } + + return mSourceAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getChannel()); + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownMacStructure.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownMacStructure.java new file mode 100644 index 000000000..236d131bb --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownMacStructure.java @@ -0,0 +1,58 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import java.util.Collections; +import java.util.List; + +/** + * Unknown MAC Opcode Structure. + */ +public class UnknownMacStructure extends MacStructure +{ + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset bit index to the start of this structure + */ + public UnknownMacStructure(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + @Override + public List getIdentifiers() + { + return Collections.EMPTY_LIST; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("UNKNOWN MAC STRUCTURE - OPCODE:").append(getOpcode().name()); + sb.append(" OPCODE VALUE:").append(getOpcodeNumber()); + sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownStructure.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownStructure.java deleted file mode 100644 index 663f7c96d..000000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownStructure.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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.phase2.message.mac.structure; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - -import java.util.Collections; -import java.util.List; - -/** - * Unknown MAC Opcode Structure. - */ -public class UnknownStructure extends MacStructure -{ - /** - * Constructs the message - * - * @param message containing the message bits - * @param offset bit index to the start of this structure - */ - public UnknownStructure(CorrectedBinaryMessage message, int offset) - { - super(message, offset); - } - - @Override - public List getIdentifiers() - { - return Collections.EMPTY_LIST; - } - - @Override - public String toString() - { - return getOpcode().toString(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownVendorMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownVendorMessage.java index 28834e236..ad907e990 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownVendorMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/UnknownVendorMessage.java @@ -1,43 +1,34 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.message.mac.structure; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; -import io.github.dsheirer.module.decode.p25.reference.Vendor; - import java.util.Collections; import java.util.List; /** - * Unknown Vendor Message + * Manufacturer / Vendor Custom Message - All Mac Opcodes from 128 - 191 */ -public class UnknownVendorMessage extends MacStructure +public class UnknownVendorMessage extends MacStructureVendor { - private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] LENGTH = {18, 19, 20, 21, 22, 23}; - /** * Constructs the message * @@ -55,27 +46,16 @@ public UnknownVendorMessage(CorrectedBinaryMessage message, int offset) public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("MANUFACTURER MESSAGE VENDOR:").append(getVendor()); - sb.append(" LENGTH:").append(getMessageLength()); - sb.append(" MSG:").append(getMessage().getSubMessage(getOffset(), getMessage().size()).toHexString()); + sb.append("CUSTOM/UNKNOWN"); + sb.append(" VENDOR:").append(getVendor()); + sb.append(" ID:").append(String.format("%02X", getVendorID())); + sb.append(" OPCODE:").append(getOpcodeNumber()); + sb.append(" LENGTH:").append(getLength()); + sb.append(" MSG:").append(getMessage().getSubMessage(getOffset(), getOffset() + (getLength() * 8)).toHexString()); return sb.toString(); } - public Vendor getVendor() - { - return Vendor.fromValue(getMessage().getInt(VENDOR, getOffset())); - } - - /** - * Length of this message - */ - public int getMessageLength() - { - return getMessage().getInt(LENGTH, getOffset()); - } - - @Override public List getIdentifiers() { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisRegroupCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisGroupRegroupExplicitEncryptionCommand.java similarity index 65% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisRegroupCommand.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisGroupRegroupExplicitEncryptionCommand.java index ff470d5ff..278923172 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisRegroupCommand.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisGroupRegroupExplicitEncryptionCommand.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -20,6 +20,7 @@ package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.patch.PatchGroup; import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; @@ -27,7 +28,7 @@ import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; import io.github.dsheirer.module.decode.p25.reference.Encryption; import io.github.dsheirer.module.decode.p25.reference.RegroupOptions; import io.github.dsheirer.module.decode.p25.reference.Vendor; @@ -36,34 +37,23 @@ import java.util.stream.Collectors; /** - * L3Harris Group Regroup Explicit Encryption Command. Provides super group activate/deactivate for dynamic regrouping - * of talkgroups or groups of individual radios and includes optional encryption algorithm and key parameters when the - * regrouping targets talkgroups (not radios). - * - * See: https://forums.radioreference.com/threads/duke-energy-p25-system.411183/page-28#post-3908078 + * L3Harris Group Regroup Explicit Encryption Command. */ -public class L3HarrisRegroupCommand extends MacStructure +public class L3HarrisGroupRegroupExplicitEncryptionCommand extends MacStructureVendor { - private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] LENGTH = {18, 19, 20, 21, 22, 23}; - private static final int[] REGROUP_OPTIONS = {24, 25, 26, 27, 28, 29, 30, 31}; - private static final int[] SUPERGROUP = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; - private static final int[] KEY_ID = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; - - //Variation 1: Radio identifiers only. - private static final int[] RADIO_1 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, - 83, 84, 85, 86, 87}; - private static final int[] RADIO_2 = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, - 106, 107, 108, 109, 110, 111}; - private static final int[] RADIO_3 = {112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, - 127, 128, 129, 130, 131, 132, 133, 134, 135}; - - //Variation 2: Talkgroups and Algorithm ID - private static final int[] ALGORITHM_ID = {64, 65, 66, 67, 68, 69, 70, 71}; - private static final int[] TALKGROUP_1 = {72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; - private static final int[] TALKGROUP_2 = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; - private static final int[] TALKGROUP_3 = {104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119}; - private static final int[] TALKGROUP_4 = {120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135}; + private static final IntField GRG_OPTIONS = IntField.length8(OCTET_4_BIT_24); + private static final IntField SUPERGROUP_ADDRESS = IntField.length16(OCTET_5_BIT_32); + private static final IntField KEY_ID = IntField.length16(OCTET_7_BIT_48); + + private static final IntField RADIO_1 = IntField.length24(OCTET_9_BIT_64); + private static final IntField RADIO_2 = IntField.length24(OCTET_12_BIT_88); + private static final IntField RADIO_3 = IntField.length24(OCTET_15_BIT_112); + + private static final IntField ALGORITHM_ID = IntField.length8(OCTET_9_BIT_64); + private static final IntField TALKGROUP_1 = IntField.length16(OCTET_10_BIT_72); + private static final IntField TALKGROUP_2 = IntField.length16(OCTET_12_BIT_88); + private static final IntField TALKGROUP_3 = IntField.length16(OCTET_14_BIT_104); + private static final IntField TALKGROUP_4 = IntField.length16(OCTET_16_BIT_120); private RegroupOptions mRegroupOptions; private PatchGroupIdentifier mPatchGroupIdentifier; @@ -75,7 +65,7 @@ public class L3HarrisRegroupCommand extends MacStructure * @param message containing the message bits * @param offset into the message for this structure */ - public L3HarrisRegroupCommand(CorrectedBinaryMessage message, int offset) + public L3HarrisGroupRegroupExplicitEncryptionCommand(CorrectedBinaryMessage message, int offset) { super(message, offset); } @@ -89,7 +79,7 @@ public String toString() if(getVendor() == Vendor.HARRIS) { - sb.append("L3HARRIS "); + sb.append("L3HARRIS"); } else { @@ -103,8 +93,6 @@ public String toString() sb.append(" SUPERGROUP:").append(getPatchGroup().getValue().getPatchGroup().getValue()); sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber()); - sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber()); - if(getRegroupOptions().isActivate()) { if(getRegroupOptions().isTalkgroupAddress()) @@ -112,14 +100,6 @@ public String toString() sb.append(" INCLUDE TALKGROUPS:"); sb.append(getPatchGroup().getValue().getPatchedTalkgroupIdentifiers().stream() .map(tg -> tg.getValue().toString()).collect(Collectors.joining(","))); - - Encryption encryption = getEncryptionAlgorithm(); - - if(encryption != Encryption.UNENCRYPTED) - { - sb.append(" USE ENCRYPTION:").append(encryption.name()); - sb.append(" KEY:").append(getKeyId()); - } } else { @@ -127,11 +107,20 @@ public String toString() sb.append(getPatchGroup().getValue().getPatchedRadioIdentifiers().stream() .map(radio -> radio.getValue().toString()).collect(Collectors.joining(","))); } + + int key = getKeyId(); + + if(key > 0) + { + sb.append(" USE ENCRYPTION:").append(getEncryptionAlgorithm()); + sb.append(" KEY:").append(key); + } } } else { sb.append(" DEACTIVATE SUPERGROUP:").append(getPatchGroup().getValue().getPatchGroup().getValue()); + sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber()); } sb.append(" MSG LENGTH:").append(getLength()); @@ -139,23 +128,6 @@ public String toString() return sb.toString(); } - /** - * Vendor ID. This should be L3Harris unless another vendor is also using this Opcode. - */ - public Vendor getVendor() - { - return Vendor.fromValue(getMessage().getInt(VENDOR, getOffset())); - } - - /** - * Message length. Note: I'm unsure if this is the count of identifiers or if this is the quantity of bytes. - * @return length. - */ - public int getLength() - { - return getMessage().getInt(LENGTH, getOffset()); - } - /** * Regrouping options for this patch group (ie activate, deactivate, patch group version, etc. * @return patch group options. @@ -164,7 +136,7 @@ public RegroupOptions getRegroupOptions() { if(mRegroupOptions == null) { - mRegroupOptions = new RegroupOptions(getMessage().getInt(REGROUP_OPTIONS, getOffset())); + mRegroupOptions = new RegroupOptions(getInt(GRG_OPTIONS)); } return mRegroupOptions; @@ -176,7 +148,7 @@ public RegroupOptions getRegroupOptions() */ public int getKeyId() { - return getMessage().getInt(KEY_ID, getOffset()); + return getInt(KEY_ID); } /** @@ -184,7 +156,15 @@ public int getKeyId() */ public Encryption getEncryptionAlgorithm() { - return Encryption.fromValue(getMessage().getInt(ALGORITHM_ID, getOffset())); + if(getRegroupOptions().isTalkgroupAddress()) + { + return Encryption.fromValue(getMessage().getInt(ALGORITHM_ID, getOffset())); + } + else + { + //The radio version of the message does not specify an algorithm. + return Encryption.UNKNOWN; + } } /** @@ -195,12 +175,12 @@ public PatchGroupIdentifier getPatchGroup() { if(mPatchGroupIdentifier == null) { - TalkgroupIdentifier patchGroupId = APCO25Talkgroup.create(getMessage().getInt(SUPERGROUP, getOffset())); + TalkgroupIdentifier patchGroupId = APCO25Talkgroup.create(getInt(SUPERGROUP_ADDRESS)); PatchGroup patchGroup = new PatchGroup(patchGroupId, getRegroupOptions().getSupergroupSequenceNumber()); if(getRegroupOptions().isTalkgroupAddress()) { - int talkgroup1 = getMessage().getInt(TALKGROUP_1, getOffset()); + int talkgroup1 = getInt(TALKGROUP_1); if(talkgroup1 > 0) { @@ -230,19 +210,19 @@ public PatchGroupIdentifier getPatchGroup() } else { - int radio1 = getMessage().getInt(RADIO_1, getOffset()); + int radio1 = getInt(RADIO_1); if(radio1 > 0) { patchGroup.addPatchedRadio(APCO25RadioIdentifier.createTo(radio1)); - int radio2 = getMessage().getInt(RADIO_2, getOffset()); + int radio2 = getInt(RADIO_2); if(radio2 > 0) { patchGroup.addPatchedRadio(APCO25RadioIdentifier.createTo(radio2)); - int radio3 = getMessage().getInt(RADIO_3, getOffset()); + int radio3 = getInt(RADIO_3); if(radio3 > 0) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisPrivateDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisPrivateDataChannelGrant.java new file mode 100644 index 000000000..2d204238b --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisPrivateDataChannelGrant.java @@ -0,0 +1,140 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.l3harris; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * L3Harris Unknown Opcode 160 (0xA0), possible Private Data Channel Grant. + * + * Observed on L3Harris control channel transmitted to a radio after the radio was on an SNDCP data channel and the + * controller was sending continuous TDULC with L3Harris Opcode 0x0A messages to the same radio. + * + * On returning to the phase 2 control channel, the following two messages were transmitted: + * + * LOCCH-U NAC:9/x009 SIGNAL CUSTOM/UNKNOWN VENDOR:HARRIS ID:A4 OPCODE:160 LENGTH:09 MSG:A0A409AC0312014871 (radio 0x014871 go to data channel 0x0312??) + * LOCCH-U NAC:9/x009 SIGNAL CUSTOM/UNKNOWN VENDOR:HARRIS ID:A4 OPCODE:172 LENGTH:12 MSG:ACA40C000312014871980418 (from 0x014871 to 0x980418 unit-2-unit data channel grant?) + * + * Both messages seem to refer to a possible channel 0-786 (0x0312) so this may be a unit-2-unit private Phase 1 call + * or maybe a private data call. Radio addresses: 0x014871 and 0x980418 + */ +public class L3HarrisPrivateDataChannelGrant extends MacStructureVendor implements IFrequencyBandReceiver, + IP25ChannelGrantDetailProvider +{ + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_5_BIT_32); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_48); + private APCO25Channel mChannel; + private RadioIdentifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public L3HarrisPrivateDataChannelGrant(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("L3HARRIS MACO:160 PRIVATE CHANNEL GRANT TO:").append(getTargetAddress()); + sb.append(" CHAN:").append(getChannel()).append(" FREQ:").append(getChannel().getDownlinkFrequency()); + sb.append(" MSG:").append(getMessage().getSubMessage(getOffset(), getOffset() + (getLength() * 8)).toHexString()); + return sb.toString(); + } + + /** + * Channel + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * Target radio for this message. + */ + public RadioIdentifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } + + @Override + public Identifier getSourceAddress() + { + return null; + } + + @Override + public ServiceOptions getServiceOptions() + { + return new DataServiceOptions(0); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerAlias.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerAlias.java index a92e3bc68..60521ac56 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerAlias.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerAlias.java @@ -22,7 +22,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.alias.P25TalkerAliasIdentifier; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; import io.github.dsheirer.module.decode.p25.reference.Vendor; import java.util.ArrayList; import java.util.List; @@ -30,12 +30,9 @@ /** * L3Harris Talker Alias. */ -public class L3HarrisTalkerAlias extends MacStructure +public class L3HarrisTalkerAlias extends MacStructureVendor { - private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] LENGTH = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int ALIAS_START = 24; - + private static final int ALIAS_START = OCTET_4_BIT_24; private List mIdentifiers; private P25TalkerAliasIdentifier mAliasIdentifier; @@ -59,7 +56,7 @@ public String toString() if(getVendor() == Vendor.HARRIS) { - sb.append("L3HARRIS "); + sb.append("L3HARRIS"); } else { @@ -78,7 +75,7 @@ public P25TalkerAliasIdentifier getAlias() { if(mAliasIdentifier == null) { - int length = getLength(); + int length = getLength() - 2; if(length > 0) { @@ -98,24 +95,6 @@ public P25TalkerAliasIdentifier getAlias() return mAliasIdentifier; } - /** - * Vendor ID. This should be L3Harris unless another vendor is also using this Opcode. - */ - public Vendor getVendor() - { - return Vendor.fromValue(getMessage().getInt(VENDOR, getOffset())); - } - - /** - * Message length. - * - * @return length in bytes, including the opcode. - */ - public int getLength() - { - return getMessage().getInt(LENGTH, getOffset()); - } - @Override public List getIdentifiers() { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisGpsLocation.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerGpsLocation.java similarity index 73% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisGpsLocation.java rename to src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerGpsLocation.java index 4b274f850..6d18fbec3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisGpsLocation.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisTalkerGpsLocation.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -20,9 +20,10 @@ package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.dmr.identifier.P25Location; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; import io.github.dsheirer.module.decode.p25.reference.Vendor; import java.text.DecimalFormat; import java.text.SimpleDateFormat; @@ -32,11 +33,11 @@ import org.jdesktop.swingx.mapviewer.GeoPosition; /** - * L3Harris GPS Location. + * L3Harris Talker GPS Location. * * Bit field definitions are best-guess from observed samples. */ -public class L3HarrisGpsLocation extends MacStructure +public class L3HarrisTalkerGpsLocation extends MacStructureVendor { public static final SimpleDateFormat SDF = new SimpleDateFormat("HH:mm:ss"); static { @@ -44,39 +45,35 @@ public class L3HarrisGpsLocation extends MacStructure } private static final DecimalFormat GPS_FORMAT = new DecimalFormat("0.000000"); private static final DecimalFormat FIXED = new DecimalFormat("000"); - private static final int[] OPCODE = {0, 1, 2, 3, 4, 5, 6, 7}; - private static final int[] UNKNOWN = {8, 9, 10, 11, 12, 13, 14, 15}; - private static final int[] VENDOR = {16, 17, 18, 19, 20, 21, 22, 23}; - private static final int[] LENGTH = {24, 25, 26, 27, 28, 29, 30, 31}; //Length is 17.5 bytes ... observed 17 here - //Bits 32 & 33 not set in sample data - seems unused for a 1/5000th of a minute number system - private static final int[] LATITUDE_MINUTES_FRACTIONAL = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46}; - private static final int LATITUDE_HEMISPHERE = 48; - //Bit 47 & 49 not set in sample data - private static final int[] LATITUDE_MINUTES = {50, 51, 52, 53, 54, 55}; - private static final int[] LATITUDE_DEGREES = {56, 57, 58, 59, 60, 61, 62, 63}; + //Bits 24 & 25 not set in sample data - seems unused for a 1/5000th of a minute number system + private static final IntField LATITUDE_MINUTES_FRACTIONAL = IntField.range(24, 38); + private static final int LATITUDE_HEMISPHERE = 40; + //Bit 39 & 41 not set in sample data + private static final IntField LATITUDE_MINUTES = IntField.range(42, 47); + private static final IntField LATITUDE_DEGREES = IntField.range(48, 55); - //Bits 64 & 65 not set in sample data - seems unused for a 1/5000th of a minute number system - private static final int[] LONGITUDE_MINUTES_FRACTIONAL = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78}; - private static final int LONGITUDE_HEMISPHERE = 80; - //Bit 79 & 81 not set in sample data - private static final int[] LONGITUDE_MINUTES = {82, 83, 84, 85, 86, 87}; - private static final int[] LONGITUDE_DEGREES = {88, 89, 90, 91, 92, 93, 94, 95}; + //Bits 56 & 57 not set in sample data - seems unused for a 1/5000th of a minute number system + private static final IntField LONGITUDE_MINUTES_FRACTIONAL = IntField.range(56, 70); + private static final int LONGITUDE_HEMISPHERE = 72; + //Bit 71 & 73 not set in sample data + private static final IntField LONGITUDE_MINUTES = IntField.range(74, 79); + private static final IntField LONGITUDE_DEGREES = IntField.range(80, 87); //There's a leading bit missing from GPS Time to get from (2^16) to (2^17) needed address space (86,400 total seconds) - private static final int[] GPS_TIME = {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}; + private static final IntField GPS_TIME = IntField.range(88, 103); - private static final int[] U2 = {112, 113, 114, 115, 116, 117, 118, 119}; - private static final int[] U3 = {120, 121, 122, 123, 124, 125, 126, 127}; - private static final int[] U4 = {128, 129, 130, 131, 132, 133, 134, 135}; - private static final int[] U5 = {136, 137, 138, 139, 140, 141, 142, 143}; - private static final int[] U6 = {144, 145, 146, 147, 148, 149, 150, 151}; - private static final int[] U7 = {152, 153, 154, 155, 156, 157, 158, 159}; - private static final int[] U8 = {160, 161, 162, 163, 164, 165, 166, 167}; + private static final IntField U1 = IntField.length8(OCTET_14_BIT_104); + private static final IntField U2 = IntField.length8(OCTET_15_BIT_112); + private static final IntField U3 = IntField.length8(OCTET_16_BIT_120); + private static final IntField U4 = IntField.length8(OCTET_17_BIT_128); + + //This may not be accurate. + private static final IntField HEADING = IntField.range(119, 127); private P25Location mLocation; - private List mIdentifiers; private GeoPosition mGeoPosition; + private List mIdentifiers; /** * Constructs the message @@ -84,18 +81,50 @@ public class L3HarrisGpsLocation extends MacStructure * @param message containing the message bits * @param offset into the message for this structure */ - public L3HarrisGpsLocation(CorrectedBinaryMessage message, int offset) + public L3HarrisTalkerGpsLocation(CorrectedBinaryMessage message, int offset) { super(message, offset); } + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(getVendor() == Vendor.HARRIS) + { + sb.append("L3H TALKER GPS "); + } + else + { + sb.append("VENDOR:").append(getVendor()).append(" TALKER GPS "); + } + + sb.append(GPS_FORMAT.format(getLatitude())).append(" ").append(GPS_FORMAT.format(getLongitude())); + sb.append(" HEADING:").append(getHeading()); + sb.append(" TIME:").append(SDF.format(getTimestampMs())); + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + /** + * Heading + * @return heading, 0-359 degrees. + */ + public int getHeading() + { + return getInt(HEADING); + } + /** * GPS Position time in milliseconds. * @return time in ms UTC */ public long getTimestampMs() { - return getMessage().getInt(GPS_TIME, getOffset()) * 1000; //Convert seconds to milliseconds. + return getInt(GPS_TIME) * 1000; //Convert seconds to milliseconds. } /** @@ -140,7 +169,7 @@ public double getLatitude() */ private double getLatitudeDegrees() { - return getMessage().getInt(LATITUDE_DEGREES, getOffset()); + return getInt(LATITUDE_DEGREES); } /** @@ -148,8 +177,7 @@ private double getLatitudeDegrees() */ private double getLatitudeMinutes() { - return getMessage().getInt(LATITUDE_MINUTES, getOffset()) + - getMessage().getInt(LATITUDE_MINUTES_FRACTIONAL, getOffset()) / 5000d; + return getInt(LATITUDE_MINUTES) + getInt(LATITUDE_MINUTES_FRACTIONAL) / 5000d; } /** @@ -166,7 +194,7 @@ public double getLongitude() */ private double getLongitudeDegrees() { - return getMessage().getInt(LONGITUDE_DEGREES, getOffset()); + return getInt(LONGITUDE_DEGREES); } /** @@ -174,47 +202,7 @@ private double getLongitudeDegrees() */ private double getLongitudeMinutes() { - return getMessage().getInt(LONGITUDE_MINUTES, getOffset()) + - getMessage().getInt(LONGITUDE_MINUTES_FRACTIONAL, getOffset()) / 5000d; - } - - /** - * Textual representation of this message - */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - - if(getVendor() == Vendor.HARRIS) - { - sb.append("L3H TALKER GPS "); - } - else - { - sb.append("VENDOR:").append(getVendor()).append(" TALKER GPS "); - } - - sb.append(GPS_FORMAT.format(getLatitude())).append(" ").append(GPS_FORMAT.format(getLongitude())); - sb.append(" TIME:").append(SDF.format(getTimestampMs())); - return sb.toString(); - } - - /** - * Vendor ID. This should be L3Harris unless another vendor is also using this Opcode. - */ - public Vendor getVendor() - { - return Vendor.fromValue(getMessage().getInt(VENDOR, getOffset())); - } - - /** - * Message length. - * - * @return length in bytes, including the opcode. - */ - public int getLength() - { - return getMessage().getInt(LENGTH, getOffset()); + return getInt(LONGITUDE_MINUTES) + getInt(LONGITUDE_MINUTES_FRACTIONAL) / 5000d; } @Override @@ -290,12 +278,17 @@ public static void main(String[] args) "8080AAA4111BBC1E250DF2AE4DC0A3022F0714FA900D9000", "8080AAA4111BBC1E250DE8AE4DC0A4022F071484F00D9000", "8080AAA4111BD01E250DE8AE4DC0A5022F07146DB00D9000", + "8480AAA411265C33200DA098600C644B01061931C0F67", //RADIO:7376955 - Rockwall,TX 2024-04-24 + "8480AAA411265B33200DA198600C65E40105193290F67", + "8480AAA411265B33200DA298600C667701061965F0F67", + "8480AAA411265A33200DA298600C670C0107195330F67", + "8C80AAA411265A33200DA298600C6C43010719087088C", }; for(String example: examples) { CorrectedBinaryMessage cbm = new CorrectedBinaryMessage(CorrectedBinaryMessage.loadHex(example)); - L3HarrisGpsLocation gps = new L3HarrisGpsLocation(cbm, 8); + L3HarrisTalkerGpsLocation gps = new L3HarrisTalkerGpsLocation(cbm, 16); System.out.println(gps); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisUnitToUnitDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisUnitToUnitDataChannelGrant.java new file mode 100644 index 000000000..14447af67 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisUnitToUnitDataChannelGrant.java @@ -0,0 +1,153 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.l3harris; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions; +import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * L3Harris Unknown Opcode 172 (0xAC), possible Unit-2-Unit Private Data Channel Grant. + * + * Observed on L3Harris control channel transmitted to a radio after the radio was on an SNDCP data channel and the + * controller was sending continuous TDULC with L3Harris Opcode 0x0A messages to the same radio. + * + * On returning to the phase 2 control channel, the following two messages were transmitted: + * + * LOCCH-U NAC:9/x009 SIGNAL CUSTOM/UNKNOWN VENDOR:HARRIS ID:A4 OPCODE:160 LENGTH:9 MSG:A0A409AC0312014871 (radio 0x014871 go to data channel 0x0312??) + * LOCCH-U NAC:9/x009 SIGNAL CUSTOM/UNKNOWN VENDOR:HARRIS ID:A4 OPCODE:172 LENGTH:12 MSG:ACA40C000312014871980418 (from 0x014871 to 0x980418 unit-2-unit data channel grant?) + * + * Both messages seem to refer to a possible channel 0-786 (0x0312) so this may be a unit-2-unit private Phase 1 call + * or maybe a private data call. Radio addresses: 0x014871 and 0x980418 + */ +public class L3HarrisUnitToUnitDataChannelGrant extends MacStructureVendor implements IFrequencyBandReceiver, + IP25ChannelGrantDetailProvider +{ + //OCTET 4 is 0x00 ? + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_5_BIT_32); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_7_BIT_48); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_10_BIT_72); + private APCO25Channel mChannel; + private RadioIdentifier mSourceAddress; + private RadioIdentifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public L3HarrisUnitToUnitDataChannelGrant(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("L3HARRIS MACO:172 PRIVATE CHANNEL GRANT TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" CHAN:").append(getChannel()); + sb.append(" MSG:").append(getMessage().getSubMessage(getOffset(), getOffset() + (getLength() * 8)).toHexString()); + return sb.toString(); + } + + /** + * Channel + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + /** + * Source radio for this message + */ + public RadioIdentifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + + /** + * Target radio for this message. + */ + public RadioIdentifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } + + @Override + public ServiceOptions getServiceOptions() + { + return new DataServiceOptions(0); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceAddress()); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisUnknownOpcode129.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisUnknownOpcode129.java new file mode 100644 index 000000000..2fd8f4749 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisUnknownOpcode129.java @@ -0,0 +1,72 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.l3harris; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.ArrayList; +import java.util.List; + +/** + * L3Harris Unknown Opcode 129 (0x81). + * + * Observed on L3Harris control channel, continuously transmitted and the message content doesn't change. + * + * 81A4120F110DFFFFFFFFFFFFFFFFFFFFFFFF on WACN 0x91F14, SYS 0x2D7, SITE 0x0A (recorded late in ~2020) + * 81A4120F110DFFFFFFFFFFFFFFFFFFFFFFFF on WACN 0x91F14, SYS 0x201, SITE 0x18 (recorded in Feb 2024) + */ +public class L3HarrisUnknownOpcode129 extends MacStructureVendor +{ + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public L3HarrisUnknownOpcode129(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("L3HARRIS UNKNOWN OPCODE 129 MSG:").append(getMessage() + .getSubMessage(getOffset(), getOffset() + (getLength() * 8)).toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisUnknownOpcode143.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisUnknownOpcode143.java new file mode 100644 index 000000000..6add6bac5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisUnknownOpcode143.java @@ -0,0 +1,74 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.l3harris; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.ArrayList; +import java.util.List; + +/** + * L3Harris Unknown Opcode 143 (0x8F). + * + * Observed on L3Harris control channel, continuously transmitted and the message content doesn't change for the + * same control channel. + * + * 8FA4070A44800A on WACN:0x91F14, SYS 0x2D7, SITE 0x0A, LRA 0x0A (recorded late in ~2020) + * 8FA4070A43800A on WACN:0x91F14, SYS 0x201, SITE 0x18, LRA 0x18 (recorded in Feb 2024) + * ^ + */ +public class L3HarrisUnknownOpcode143 extends MacStructureVendor +{ + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public L3HarrisUnknownOpcode143(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("L3HARRIS UNKNOWN OPCODE 143 MSG:").append(getMessage() + .getSubMessage(getOffset(), getOffset() + (getLength() * 8)).toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/UnknownOpcode136.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/UnknownOpcode136.java new file mode 100644 index 000000000..44a8dc6db --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/UnknownOpcode136.java @@ -0,0 +1,79 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.l3harris; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.ArrayList; +import java.util.List; + +/** + * Unknown Opcode 136 (0x88) + * + * Observed on L3Harris control channel. Opcode is in the vendor partition, but the message indicates a + * standard (0x00) vendor ID. + * + * Doesn't seem to correlate with any activity. Observed the following sequence of 4 messages repeatedly, normally + * occurring in either the 1/3 or 3/3 superframe fragment, but this may be due to the MAC scheduler in the transmitter: + * + * 8800AB7BE + * 8800A8952 + * 8800A6BBF + * 8800A0F7B + * + * Observed on Duke Energy Ohio Site 0x0A + */ +public class UnknownOpcode136 extends MacStructureVendor +{ + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public UnknownOpcode136(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("UNKNOWN OPCODE 136 VENDOR:STANDARD MSG:").append(getMessage().getSubMessage(getOffset(), getOffset() + 36).toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaAcknowledgeResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaAcknowledgeResponse.java new file mode 100644 index 000000000..000d1b582 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaAcknowledgeResponse.java @@ -0,0 +1,107 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola acknowledge response + */ +public class MotorolaAcknowledgeResponse extends MacStructureVendor +{ + private static final IntField SERVICE_TYPE = IntField.range(26, 31); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_5_BIT_32); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_8_BIT_56); + + private Identifier mSourceAddress; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaAcknowledgeResponse(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" FM:").append(getSourceAddress()); + sb.append(" SERVICE:").append(getServiceType()); + return sb.toString(); + } + + /** + * Opcode representing the service type that is being acknowledged to the radio unit. + */ + public MacOpcode getServiceType() + { + return MacOpcode.fromValue(getInt(SERVICE_TYPE)); + } + + public Identifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaDenyResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaDenyResponse.java new file mode 100644 index 000000000..7df7fcb9f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaDenyResponse.java @@ -0,0 +1,132 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.DenyReason; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola deny response + */ +public class MotorolaDenyResponse extends MacStructureVendor +{ + private static final int ADDITIONAL_INFORMATION_INDICATOR = 24; + private static final IntField SERVICE_TYPE = IntField.range(26, 31); + private static final IntField REASON = IntField.length8(OCTET_5_BIT_32); + private static final IntField ADDITIONAL_INFO = IntField.length24(OCTET_6_BIT_40); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_9_BIT_64); + + private DenyReason mDenyReason; + private String mAdditionalInfo; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaDenyResponse(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" SERVICE:").append(getDeniedServiceType()); + sb.append(" REASON:").append(getDenyReason()); + + if(hasAdditionalInformation()) + { + sb.append(" INFO:").append(getAdditionalInfo()); + } + + return sb.toString(); + } + + public boolean hasAdditionalInformation() + { + return getMessage().get(ADDITIONAL_INFORMATION_INDICATOR + getOffset()); + } + + public String getAdditionalInfo() + { + if(mAdditionalInfo == null) + { + mAdditionalInfo = Integer.toHexString(getInt(ADDITIONAL_INFO)).toUpperCase(); + } + + return mAdditionalInfo; + } + + /** + * Opcode representing the service type that is being denied to the radio unit. + */ + public MacOpcode getDeniedServiceType() + { + return MacOpcode.fromValue(getInt(SERVICE_TYPE)); + } + + public DenyReason getDenyReason() + { + if(mDenyReason == null) + { + mDenyReason = DenyReason.fromCode(getInt(REASON)); + } + + return mDenyReason; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupAddCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupAddCommand.java new file mode 100644 index 000000000..d625f1cbe --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupAddCommand.java @@ -0,0 +1,151 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Group Regroup Add Command. + */ +public class MotorolaGroupRegroupAddCommand extends MacStructureVendor +{ + private static final IntField SUPERGROUP_ADDRESS = IntField.length16(OCTET_4_BIT_24); + private static final IntField TALKGROUP_1 = IntField.length16(OCTET_6_BIT_40); + private static final IntField TALKGROUP_2 = IntField.length16(OCTET_8_BIT_56); + private static final IntField TALKGROUP_3 = IntField.length16(OCTET_10_BIT_72); + private static final IntField TALKGROUP_4 = IntField.length16(OCTET_12_BIT_88); + private static final IntField TALKGROUP_5 = IntField.length16(OCTET_14_BIT_104); + private static final IntField TALKGROUP_6 = IntField.length16(OCTET_16_BIT_120); + private PatchGroupIdentifier mPatchGroupIdentifier; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupAddCommand(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" PATCHGROUP:").append(getPatchGroup()); + return sb.toString(); + } + + /** + * Supergroup/Patch group referenced by this message including any patched talkgroups or individual radio identifiers. + * @return patch group. + */ + public PatchGroupIdentifier getPatchGroup() + { + if(mPatchGroupIdentifier == null) + { + TalkgroupIdentifier patchGroupId = APCO25Talkgroup.create(getInt(SUPERGROUP_ADDRESS)); + PatchGroup patchGroup = new PatchGroup(patchGroupId, 0); + + int tg1 = getInt(TALKGROUP_1); + if(tg1 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(tg1)); + } + + int length = getLength(); + + if(length >= 9) + { + int tg2 = getInt(TALKGROUP_2); + if(tg2 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(tg2)); + } + } + + if(length >= 11) + { + int tg3 = getInt(TALKGROUP_3); + if(tg3 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(tg3)); + } + } + + if(length >= 13) + { + int tg4 = getInt(TALKGROUP_4); + if(tg4 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(tg4)); + } + } + + if(length >= 15) + { + int tg5 = getInt(TALKGROUP_5); + if(tg5 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(tg5)); + } + } + + if(length >= 17) + { + int tg6 = getInt(TALKGROUP_6); + if(tg6 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(tg6)); + } + } + mPatchGroupIdentifier = APCO25PatchGroup.create(patchGroup); + } + + return mPatchGroupIdentifier; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupChannelGrantExplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupChannelGrantExplicit.java new file mode 100644 index 000000000..d31578564 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupChannelGrantExplicit.java @@ -0,0 +1,172 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +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.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Group Regroup Channel Grant Explicit + */ +public class MotorolaGroupRegroupChannelGrantExplicit extends MacStructureVendor + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider, IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_4_BIT_24); + private static final IntField TRANSMIT_FREQUENCY_BAND = IntField.length4(OCTET_5_BIT_32); + private static final IntField TRANSMIT_CHANNEL_NUMBER = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField RECEIVE_FREQUENCY_BAND = IntField.length4(OCTET_7_BIT_48); + private static final IntField RECEIVE_CHANNEL_NUMBER = IntField.length12(OCTET_7_BIT_48 + 4); + private static final IntField SUPERGROUP_ADDRESS = IntField.length16(OCTET_9_BIT_64); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_11_BIT_80); + + private VoiceServiceOptions mServiceOptions; + private List mIdentifiers; + private PatchGroupIdentifier mPatchgroup; + private RadioIdentifier mSourceAddress; + private APCO25Channel mChannel; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupChannelGrantExplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + if(getServiceOptions().isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + sb.append(" SUPERGROUP:").append(getTargetAddress()); + if(hasSourceAddress()) + { + sb.append(" SOURCE:").append(getSourceAddress()); + } + return sb.toString(); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25ExplicitChannel.create(getInt(TRANSMIT_FREQUENCY_BAND), getInt(TRANSMIT_CHANNEL_NUMBER), + getInt(RECEIVE_FREQUENCY_BAND), getInt(RECEIVE_CHANNEL_NUMBER)); + } + + return mChannel; + } + + + /** + * Service options for the referenced call. + */ + public VoiceServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + /** + * Patch group for the channel grant + */ + public PatchGroupIdentifier getTargetAddress() + { + if(mPatchgroup == null) + { + mPatchgroup = APCO25PatchGroup.create(getInt(SUPERGROUP_ADDRESS)); + } + + return mPatchgroup; + } + + /** + * Talker radio identifier. + */ + public RadioIdentifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * Indicates if this message has a non-zero radio talker identifier. + */ + public boolean hasSourceAddress() + { + return getInt(SOURCE_ADDRESS) > 0; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + + if(hasSourceAddress()) + { + mIdentifiers.add(getSourceAddress()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupChannelGrantImplicit.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupChannelGrantImplicit.java new file mode 100644 index 000000000..390e66dba --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupChannelGrantImplicit.java @@ -0,0 +1,168 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.IP25ChannelGrantDetailProvider; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Group Regroup Channel Grant Implicit + */ +public class MotorolaGroupRegroupChannelGrantImplicit extends MacStructureVendor + implements IFrequencyBandReceiver, IP25ChannelGrantDetailProvider, IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_4_BIT_24); + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_5_BIT_32); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_5_BIT_32 + 4); + private static final IntField SUPERGROUP_ADDRESS = IntField.length16(OCTET_7_BIT_48); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_9_BIT_64); + + private VoiceServiceOptions mServiceOptions; + private List mIdentifiers; + private PatchGroupIdentifier mPatchgroup; + private RadioIdentifier mSourceAddress; + private APCO25Channel mChannel; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupChannelGrantImplicit(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + if(getServiceOptions().isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + sb.append(" SUPERGROUP:").append(getTargetAddress()); + if(hasSourceAddress()) + { + sb.append(" SOURCE:").append(getSourceAddress()); + } + return sb.toString(); + } + + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + + /** + * Service options for the referenced call. + */ + public VoiceServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + /** + * Patch group for the channel grant + */ + public PatchGroupIdentifier getTargetAddress() + { + if(mPatchgroup == null) + { + mPatchgroup = APCO25PatchGroup.create(getInt(SUPERGROUP_ADDRESS)); + } + + return mPatchgroup; + } + + /** + * Talker radio identifier. + */ + public RadioIdentifier getSourceAddress() + { + if(mSourceAddress == null) + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } + + return mSourceAddress; + } + + /** + * Indicates if this message has a non-zero radio talker identifier. + */ + public boolean hasSourceAddress() + { + return getInt(SOURCE_ADDRESS) > 0; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + + if(hasSourceAddress()) + { + mIdentifiers.add(getSourceAddress()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupChannelGrantUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupChannelGrantUpdate.java new file mode 100644 index 000000000..53b4e79a6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupChannelGrantUpdate.java @@ -0,0 +1,172 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Group Regroup Channel Update + */ +public class MotorolaGroupRegroupChannelGrantUpdate extends MacStructureVendor implements IFrequencyBandReceiver +{ + private static final IntField FREQUENCY_BAND_A = IntField.length4(OCTET_4_BIT_24); + private static final IntField CHANNEL_NUMBER_A = IntField.length12(OCTET_4_BIT_24 + 4); + private static final IntField SUPERGROUP_ADDRESS_A = IntField.length16(OCTET_6_BIT_40); + private static final IntField FREQUENCY_BAND_B = IntField.length4(OCTET_8_BIT_56); + private static final IntField CHANNEL_NUMBER_B = IntField.length12(OCTET_8_BIT_56 + 4); + private static final IntField SUPERGROUP_ADDRESS_B = IntField.length16(OCTET_10_BIT_72); + + private List mIdentifiers; + private PatchGroupIdentifier mPatchgroupA; + private PatchGroupIdentifier mPatchgroupB; + private APCO25Channel mChannelA; + private APCO25Channel mChannelB; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupChannelGrantUpdate(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" SUPERGROUP A:").append(getPatchgroupA()); + sb.append(" CHAN A:").append(getChannelA()); + + if(hasPatchgroupB()) + { + sb.append(" SUPERGROUP B:").append(getPatchgroupB()); + sb.append(" CHAN B:").append(getChannelB()); + } + return sb.toString(); + } + + public APCO25Channel getChannelA() + { + if(mChannelA == null) + { + mChannelA = APCO25Channel.create(getInt(FREQUENCY_BAND_A), getInt(CHANNEL_NUMBER_A)); + } + + return mChannelA; + } + + public APCO25Channel getChannelB() + { + if(mChannelB == null) + { + mChannelB = APCO25Channel.create(getInt(FREQUENCY_BAND_B), getInt(CHANNEL_NUMBER_B)); + } + + return mChannelB; + } + + /** + * Patch group A + */ + public PatchGroupIdentifier getPatchgroupA() + { + if(mPatchgroupA == null) + { + mPatchgroupA = APCO25PatchGroup.create(getInt(SUPERGROUP_ADDRESS_A)); + } + + return mPatchgroupA; + } + + /** + * Patch group B + */ + public PatchGroupIdentifier getPatchgroupB() + { + if(mPatchgroupB == null) + { + mPatchgroupB = APCO25PatchGroup.create(getInt(SUPERGROUP_ADDRESS_B)); + } + + return mPatchgroupB; + } + + /** + * Indicates if this message has a second (B) patchgroup that is being reported. + * @return + */ + public boolean hasPatchgroupB() + { + int patchgroupB = getInt(SUPERGROUP_ADDRESS_B); + return patchgroupB > 0 && patchgroupB != getInt(SUPERGROUP_ADDRESS_A); + } + + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchgroupA()); + mIdentifiers.add(getChannelA()); + + if(hasPatchgroupB()) + { + mIdentifiers.add(getPatchgroupB()); + mIdentifiers.add(getChannelB()); + } + } + + return mIdentifiers; + } + + @Override + public List getChannels() + { + List channels = new ArrayList<>(); + channels.add(getChannelA()); + + if(hasPatchgroupB()) + { + channels.add(getChannelB()); + } + + return channels; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupDeleteCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupDeleteCommand.java new file mode 100644 index 000000000..30450f09c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupDeleteCommand.java @@ -0,0 +1,166 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Group Regroup Delete Command. + */ +public class MotorolaGroupRegroupDeleteCommand extends MacStructureVendor +{ + private static final IntField SUPERGROUP_ADDRESS = IntField.length16(OCTET_4_BIT_24); + private static final IntField TALKGROUP_1 = IntField.length16(OCTET_6_BIT_40); + private static final IntField TALKGROUP_2 = IntField.length16(OCTET_8_BIT_56); + private static final IntField TALKGROUP_3 = IntField.length16(OCTET_10_BIT_72); + private static final IntField TALKGROUP_4 = IntField.length16(OCTET_12_BIT_88); + private static final IntField TALKGROUP_5 = IntField.length16(OCTET_14_BIT_104); + private static final IntField TALKGROUP_6 = IntField.length16(OCTET_16_BIT_120); + private PatchGroupIdentifier mPatchGroupIdentifier; + private List mDeletedTalkgroups; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupDeleteCommand(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" PATCHGROUP:").append(getPatchGroup()); + sb.append(" DELETE:").append(getDeletedTalkgroups()); + return sb.toString(); + } + + /** + * Supergroup/Patch group referenced by this message including any patched talkgroups or individual radio identifiers. + * @return patch group. + */ + public PatchGroupIdentifier getPatchGroup() + { + if(mPatchGroupIdentifier == null) + { + TalkgroupIdentifier patchGroupId = APCO25Talkgroup.create(getInt(SUPERGROUP_ADDRESS)); + PatchGroup patchGroup = new PatchGroup(patchGroupId, 0); + mPatchGroupIdentifier = APCO25PatchGroup.create(patchGroup); + } + + return mPatchGroupIdentifier; + } + + /** + * Deleted talkgroups that shall be removed from the patch group + */ + public List getDeletedTalkgroups() + { + if(mDeletedTalkgroups == null) + { + mDeletedTalkgroups = new ArrayList<>(); + + int tg1 = getInt(TALKGROUP_1); + if(tg1 > 0) + { + mDeletedTalkgroups.add(APCO25Talkgroup.create(tg1)); + } + + int length = getLength(); + + if(length >= 9) + { + int tg2 = getInt(TALKGROUP_2); + if(tg2 > 0) + { + mDeletedTalkgroups.add(APCO25Talkgroup.create(tg2)); + } + } + + if(length >= 11) + { + int tg3 = getInt(TALKGROUP_3); + if(tg3 > 0) + { + mDeletedTalkgroups.add(APCO25Talkgroup.create(tg3)); + } + } + + if(length >= 13) + { + int tg4 = getInt(TALKGROUP_4); + if(tg4 > 0) + { + mDeletedTalkgroups.add(APCO25Talkgroup.create(tg4)); + } + } + + if(length >= 15) + { + int tg5 = getInt(TALKGROUP_5); + if(tg5 > 0) + { + mDeletedTalkgroups.add(APCO25Talkgroup.create(tg5)); + } + } + + if(length >= 17) + { + int tg6 = getInt(TALKGROUP_6); + if(tg6 > 0) + { + mDeletedTalkgroups.add(APCO25Talkgroup.create(tg6)); + } + } + } + + return mDeletedTalkgroups; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupExtendedFunctionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupExtendedFunctionCommand.java new file mode 100644 index 000000000..b751ce9b4 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupExtendedFunctionCommand.java @@ -0,0 +1,140 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.ArgumentType; +import io.github.dsheirer.module.decode.p25.reference.ExtendedFunction; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Group Regroup Extended Function Command - Create or Cancel Supergroup + */ +public class MotorolaGroupRegroupExtendedFunctionCommand extends MacStructureVendor +{ + private static final IntField EXTENDED_FUNCTION = IntField.length16(OCTET_4_BIT_24); //Class & Operand + private static final IntField ARGUMENTS = IntField.length24(OCTET_6_BIT_40); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_9_BIT_64); + private ExtendedFunction mExtendedFunction; + private Identifier mArgumentAddress; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupExtendedFunctionCommand(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + ExtendedFunction function = getExtendedFunction(); + + if(function == ExtendedFunction.UNKNOWN) + { + sb.append(" UNKNOWN FUNCTION CODE:0x").append(Integer.toHexString(getInt(EXTENDED_FUNCTION)).toUpperCase()); + } + else + { + sb.append(" ").append(function); + } + + if(getArgumentAddress() != null) + { + sb.append(" ").append(getArgumentAddress()); + } + + return sb.toString(); + } + + + public ExtendedFunction getExtendedFunction() + { + if(mExtendedFunction == null) + { + mExtendedFunction = ExtendedFunction.fromValue(getInt(EXTENDED_FUNCTION)); + } + + return mExtendedFunction; + } + + public Identifier getArgumentAddress() + { + if(mArgumentAddress == null) + { + ArgumentType type = getExtendedFunction().getArgumentType(); + + switch(type) + { + case TALKGROUP: + mArgumentAddress = APCO25Talkgroup.create(getInt(ARGUMENTS)); + case SOURCE_RADIO: + mArgumentAddress = APCO25RadioIdentifier.createFrom(getInt(ARGUMENTS)); + case TARGET_RADIO: + mArgumentAddress = APCO25RadioIdentifier.createTo(getInt(ARGUMENTS)); + default: + //Do nothing and leave the variable null. + } + } + + return mArgumentAddress; + } + + /** + * To Talkgroup + */ + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = Collections.singletonList(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupOpcode145.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupOpcode145.java new file mode 100644 index 000000000..3f8bb30f5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupOpcode145.java @@ -0,0 +1,108 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Group Regroup Unknown Opcode 145 - 17-bytes long + */ +public class MotorolaGroupRegroupOpcode145 extends MacStructureVendor +{ + private static final IntField TALKGROUP = IntField.length16(24); + private static final IntField UNKNOWN = IntField.length32(40); + private static final IntField SOURCE_SUID_WACN = IntField.length20(72); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(92); + private static final IntField SOURCE_SUID_UNIT = IntField.length24(104); + private List mIdentifiers; + private TalkgroupIdentifier mTalkgroup; + private APCO25FullyQualifiedRadioIdentifier mRadio; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupOpcode145(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA GROUP REGROUP UNKNOWN OPCODE 145 TALKGROUP:").append(getTalkgroup()); + sb.append(" SOURCE SUID RADIO:").append(getRadio()); + sb.append(" UNK:").append(Integer.toHexString(getInt(UNKNOWN)).toUpperCase()); + sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString()); + return sb.toString(); + } + + /** + * Talkgroup identifier + */ + public TalkgroupIdentifier getTalkgroup() + { + if(mTalkgroup == null) + { + mTalkgroup = APCO25Talkgroup.create(getMessage().getInt(TALKGROUP, getOffset())); + } + + return mTalkgroup; + } + + public APCO25FullyQualifiedRadioIdentifier getRadio() + { + if(mRadio == null) + { + int wacn = getMessage().getInt(SOURCE_SUID_WACN, getOffset()); + int system = getMessage().getInt(SOURCE_SUID_SYSTEM, getOffset()); + int unit = getMessage().getInt(SOURCE_SUID_UNIT, getOffset()); + mRadio = APCO25FullyQualifiedRadioIdentifier.createFrom(unit, wacn, system, unit); + } + + return mRadio; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTalkgroup()); + mIdentifiers.add(getRadio()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupVoiceChannelUpdate.java new file mode 100644 index 000000000..56d6644b3 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupVoiceChannelUpdate.java @@ -0,0 +1,142 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Group Regroup Voice Channel Update + *

+ * Indicates supergroup call activity on another (traffic) channel. + */ +public class MotorolaGroupRegroupVoiceChannelUpdate extends MacStructureVendor implements IFrequencyBandReceiver, IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_3_BIT_16); + private static final IntField SUPERGROUP = IntField.length16(OCTET_4_BIT_24); + private static final IntField FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_40); + private static final IntField CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_40 + 4); + + private VoiceServiceOptions mServiceOptions; + private List mIdentifiers; + private PatchGroupIdentifier mPatchgroup; + private APCO25Channel mChannel; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupVoiceChannelUpdate(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA GROUP REGROUP VOICE CHANNEL UPDATE"); + if(getServiceOptions().isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + sb.append(" SUPERGROUP:").append(getPatchgroup()); + sb.append(" IS ACTIVE ON CHANNEL:").append(getChannel()); + return sb.toString(); + } + + /** + * Service options for the referenced call. + */ + public VoiceServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + /** + * Talkgroup active on this channel/timeslot. + */ + public PatchGroupIdentifier getPatchgroup() + { + if(mPatchgroup == null) + { + mPatchgroup = APCO25PatchGroup.create(getInt(SUPERGROUP)); + } + + return mPatchgroup; + } + + /** + * Current channel where this call is taking place. + */ + public APCO25Channel getChannel() + { + if(mChannel == null) + { + mChannel = APCO25Channel.create(getInt(FREQUENCY_BAND), getInt(CHANNEL_NUMBER)); + } + + return mChannel; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchgroup()); + } + + return mIdentifiers; + } + + /** + * Implements the IFrequencyBandReceiver interface to expose the channel to be enriched with frequency band info. + */ + @Override + public List getChannels() + { + return Collections.singletonList(getChannel()); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupVoiceChannelUserAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupVoiceChannelUserAbbreviated.java new file mode 100644 index 000000000..8c883601e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupVoiceChannelUserAbbreviated.java @@ -0,0 +1,143 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Group Regroup Voice Channel User Abbreviated + */ +public class MotorolaGroupRegroupVoiceChannelUserAbbreviated extends MacStructureVendor implements IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_3_BIT_16); + private static final IntField SUPERGROUP = IntField.length16(OCTET_4_BIT_24); + private static final IntField SOURCE = IntField.length24(OCTET_6_BIT_40); + private VoiceServiceOptions mServiceOptions; + private List mIdentifiers; + private PatchGroupIdentifier mPatchGroup; + private RadioIdentifier mRadio; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupVoiceChannelUserAbbreviated(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA GROUP REGROUP VOICE CHANNEL USER ABBREVIATED"); + if(getServiceOptions().isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + sb.append(" SUPERGROUP:").append(getPatchGroup()); + if(hasRadio()) + { + sb.append(" FM:").append(getRadio()); + } + return sb.toString(); + } + + /** + * Service options for the referenced call. + */ + public VoiceServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + /** + * Talkgroup active on this channel/timeslot. + */ + public PatchGroupIdentifier getPatchGroup() + { + if(mPatchGroup == null) + { + mPatchGroup = APCO25PatchGroup.create(getInt(SUPERGROUP)); + } + + return mPatchGroup; + } + + /** + * Talker radio identifier. + */ + public RadioIdentifier getRadio() + { + if(mRadio == null) + { + mRadio = APCO25RadioIdentifier.createFrom(getInt(SOURCE)); + } + + return mRadio; + } + + /** + * Indicates if this message has a non-zero radio talker identifier. + */ + public boolean hasRadio() + { + return getInt(SOURCE) > 0; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + + if(hasRadio()) + { + mIdentifiers.add(getRadio()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupVoiceChannelUserExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupVoiceChannelUserExtended.java new file mode 100644 index 000000000..c8a7551e6 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaGroupRegroupVoiceChannelUserExtended.java @@ -0,0 +1,142 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Group Regroup Voice Channel User Extended + */ +public class MotorolaGroupRegroupVoiceChannelUserExtended extends MacStructureVendor implements IServiceOptionsProvider +{ + private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_4_BIT_24); + private static final IntField SUPERGROUP_ADDRESS = IntField.length16(OCTET_5_BIT_32); + private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_7_BIT_48); + private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_10_BIT_72); + private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_12_BIT_88 + 4); + private static final IntField SOURCE_SUID_ID = IntField.length24(OCTET_14_BIT_104); + private VoiceServiceOptions mServiceOptions; + private List mIdentifiers; + private PatchGroupIdentifier mPatchgroup; + private APCO25FullyQualifiedRadioIdentifier mSource; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaGroupRegroupVoiceChannelUserExtended(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA GROUP REGROUP VOICE CHANNEL USER EXTENDED"); + if(getServiceOptions().isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + sb.append(" FM:").append(getSource()); + sb.append(" TO:").append(getPatchgroup()); + return sb.toString(); + } + + /** + * Service options for this call. + */ + public VoiceServiceOptions getServiceOptions() + { + if(mServiceOptions == null) + { + mServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS)); + } + + return mServiceOptions; + } + + /** + * Supergroup/Talkgroup active on this channel/timeslot. + */ + public PatchGroupIdentifier getPatchgroup() + { + if(mPatchgroup == null) + { + mPatchgroup = APCO25PatchGroup.create(getInt(SUPERGROUP_ADDRESS)); + } + + return mPatchgroup; + } + + /** + * Indicates if this message has a non-zero radio talker identifier. + */ + public boolean hasRadio() + { + return getInt(SOURCE_ADDRESS) > 0; + } + + public FullyQualifiedRadioIdentifier getSource() + { + if(mSource == null) + { + int localAddress = getInt(SOURCE_ADDRESS); + int wacn = getInt(SOURCE_SUID_WACN); + int system = getInt(SOURCE_SUID_SYSTEM); + int id = getInt(SOURCE_SUID_ID); + mSource = APCO25FullyQualifiedRadioIdentifier.createFrom(localAddress, wacn, system, id); + } + + return mSource; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchgroup()); + + if(hasRadio()) + { + mIdentifiers.add(getSource()); + } + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaOpcode149.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaOpcode149.java new file mode 100644 index 000000000..317ad2c50 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaOpcode149.java @@ -0,0 +1,77 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Unknown Opcode 149 (0x95) + * + * This was observed at the end of a Group Regroup call sequence during HANGTIME, so it seems more of an announcement + * type message and less as something relevant to the call. + * + * Examples: + * 959011 018E58B82BAB4D3B70E9A8457F9D C67 + * 959011 0287000000000000000000000000 E26 + * + * The opcode, vendor and length octets are consistent. The first octet seems to be an identifier, 01, 02, etc. + * Nothing else seems to match any of the other identifiers that were active at call time. + */ +public class MotorolaOpcode149 extends MacStructureVendor +{ + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaOpcode149(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA UNKNOWN OPCODE 149"); + sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaQueuedResponse.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaQueuedResponse.java new file mode 100644 index 000000000..a2934b7fd --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaQueuedResponse.java @@ -0,0 +1,132 @@ +/* + * ***************************************************************************** + * 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.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import io.github.dsheirer.module.decode.p25.reference.QueuedResponseReason; +import java.util.ArrayList; +import java.util.List; + +/** + * Motorola Queued response + */ +public class MotorolaQueuedResponse extends MacStructureVendor +{ + private static final int ADDITIONAL_INFORMATION_INDICATOR = 24; + private static final IntField SERVICE_TYPE = IntField.range(26, 31); + private static final IntField REASON = IntField.length8(OCTET_5_BIT_32); + private static final IntField ADDITIONAL_INFO = IntField.length24(OCTET_6_BIT_40); + private static final IntField TARGET_ADDRESS = IntField.length24(OCTET_9_BIT_64); + + private QueuedResponseReason mQueuedResponseReason; + private String mAdditionalInfo; + private Identifier mTargetAddress; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaQueuedResponse(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getOpcode()); + sb.append(" TO:").append(getTargetAddress()); + sb.append(" SERVICE:").append(getQueuedResponseServiceType()); + sb.append(" REASON:").append(getQueuedResponseReason()); + + if(hasAdditionalInformation()) + { + sb.append(" INFO:").append(getAdditionalInfo()); + } + + return sb.toString(); + } + + private boolean hasAdditionalInformation() + { + return getMessage().get(ADDITIONAL_INFORMATION_INDICATOR + getOffset()); + } + + public String getAdditionalInfo() + { + if(mAdditionalInfo == null) + { + mAdditionalInfo = Integer.toHexString(getInt(ADDITIONAL_INFO)).toUpperCase(); + } + + return mAdditionalInfo; + } + + /** + * Opcode representing the service type that is being acknowledged by the radio unit. + */ + public MacOpcode getQueuedResponseServiceType() + { + return MacOpcode.fromValue(getInt(SERVICE_TYPE)); + } + + public QueuedResponseReason getQueuedResponseReason() + { + if(mQueuedResponseReason == null) + { + mQueuedResponseReason = QueuedResponseReason.fromCode(getInt(REASON)); + } + + return mQueuedResponseReason; + } + + public Identifier getTargetAddress() + { + if(mTargetAddress == null) + { + mTargetAddress = APCO25RadioIdentifier.createTo(getInt(TARGET_ADDRESS)); + } + + return mTargetAddress; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getTargetAddress()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/FacchTimeslot.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/FacchTimeslot.java index 5320b011b..2ef441d30 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/FacchTimeslot.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/FacchTimeslot.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -21,11 +21,14 @@ import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.FragmentedIntField; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.edac.ReedSolomon_63_35_29_P25; import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessageFactory; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.UnknownMacMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureFailedRS; +import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,52 +39,53 @@ public class FacchTimeslot extends AbstractSignalingTimeslot { private final static Logger mLog = LoggerFactory.getLogger(FacchTimeslot.class); + private static final int MAX_OCTET_INDEX = 144; //156-12 = message length minus CRC-12 checksum. - private static final int[] INFO_1 = {2,3,4,5,6,7}; - private static final int[] INFO_2 = {8,9,10,11,12,13}; - private static final int[] INFO_3 = {14,15,16,17,18,19}; - private static final int[] INFO_4 = {20,21,22,23,24,25}; - private static final int[] INFO_5 = {26,27,28,29,30,31}; - private static final int[] INFO_6 = {32,33,34,35,36,37}; - private static final int[] INFO_7 = {38,39,40,41,42,43}; - private static final int[] INFO_8 = {44,45,46,47,48,49}; - private static final int[] INFO_9 = {50,51,52,53,54,55}; - private static final int[] INFO_10 = {56,57,58,59,60,61}; - private static final int[] INFO_11 = {62,63,64,65,66,67}; - private static final int[] INFO_12 = {68,69,70,71,72,73}; //Gap for duid 74-75 - private static final int[] INFO_13 = {76,77,78,79,80,81}; - private static final int[] INFO_14 = {82,83,84,85,86,87}; - private static final int[] INFO_15 = {88,89,90,91,92,93}; - private static final int[] INFO_16 = {94,95,96,97,98,99}; - private static final int[] INFO_17 = {100,101,102,103,104,105}; - private static final int[] INFO_18 = {106,107,108,109,110,111}; - private static final int[] INFO_19 = {112,113,114,115,116,117}; - private static final int[] INFO_20 = {118,119,120,121,122,123}; - private static final int[] INFO_21 = {124,125,126,127,128,129}; - private static final int[] INFO_22 = {130,131,132,133,134,135}; - private static final int[] INFO_23 = {136,137,180,181,182,183}; //Gap for sync 138-179 - private static final int[] INFO_24 = {184,185,186,187,188,189}; - private static final int[] INFO_25 = {190,191,192,193,194,195}; - private static final int[] INFO_26 = {196,197,198,199,200,201}; - private static final int[] PARITY_1 = {202,203,204,205,206,207}; - private static final int[] PARITY_2 = {208,209,210,211,212,213}; - private static final int[] PARITY_3 = {214,215,216,217,218,219}; - private static final int[] PARITY_4 = {220,221,222,223,224,225}; - private static final int[] PARITY_5 = {226,227,228,229,230,231}; - private static final int[] PARITY_6 = {232,233,234,235,236,237}; - private static final int[] PARITY_7 = {238,239,240,241,242,243}; //Gap for duid 244-245 - private static final int[] PARITY_8 = {246,247,248,249,250,251}; - private static final int[] PARITY_9 = {252,253,254,255,256,257}; - private static final int[] PARITY_10 = {258,259,260,261,262,263}; - private static final int[] PARITY_11 = {264,265,266,267,268,269}; - private static final int[] PARITY_12 = {270,271,272,273,274,275}; - private static final int[] PARITY_13 = {276,277,278,279,280,281}; - private static final int[] PARITY_14 = {282,283,284,285,286,287}; - private static final int[] PARITY_15 = {288,289,290,291,292,293}; - private static final int[] PARITY_16 = {294,295,296,297,298,299}; - private static final int[] PARITY_17 = {300,301,302,303,304,305}; - private static final int[] PARITY_18 = {306,307,308,309,310,311}; - private static final int[] PARITY_19 = {312,313,314,315,316,317}; + private static final IntField INFO_1 = IntField.range(2, 7); + private static final IntField INFO_2 = IntField.range(8, 13); + private static final IntField INFO_3 = IntField.range(14, 19); + private static final IntField INFO_4 = IntField.range(20, 25); + private static final IntField INFO_5 = IntField.range(26, 31); + private static final IntField INFO_6 = IntField.range(32, 37); + private static final IntField INFO_7 = IntField.range(38, 43); + private static final IntField INFO_8 = IntField.range(44, 49); + private static final IntField INFO_9 = IntField.range(50, 55); + private static final IntField INFO_10 = IntField.range(56, 61); + private static final IntField INFO_11 = IntField.range(62, 67); + private static final IntField INFO_12 = IntField.range(68, 73); //Gap for duid 74-75 + private static final IntField INFO_13 = IntField.range(76, 81); + private static final IntField INFO_14 = IntField.range(82, 87); + private static final IntField INFO_15 = IntField.range(88, 93); + private static final IntField INFO_16 = IntField.range(94, 99); + private static final IntField INFO_17 = IntField.range(100, 105); + private static final IntField INFO_18 = IntField.range(106, 111); + private static final IntField INFO_19 = IntField.range(112, 117); + private static final IntField INFO_20 = IntField.range(118, 123); + private static final IntField INFO_21 = IntField.range(124, 129); + private static final IntField INFO_22 = IntField.range(130, 135); + private static final FragmentedIntField INFO_23 = FragmentedIntField.of(136, 137, 180, 181, 182, 183); //Gap for sync 138-179 + private static final IntField INFO_24 = IntField.range(184, 189); + private static final IntField INFO_25 = IntField.range(190, 195); + private static final IntField INFO_26 = IntField.range(196, 201); + private static final IntField PARITY_1 = IntField.range(202, 207); + private static final IntField PARITY_2 = IntField.range(208, 213); + private static final IntField PARITY_3 = IntField.range(214, 219); + private static final IntField PARITY_4 = IntField.range(220, 225); + private static final IntField PARITY_5 = IntField.range(226, 231); + private static final IntField PARITY_6 = IntField.range(232, 237); + private static final IntField PARITY_7 = IntField.range(238, 243); //Gap for duid 244-245 + private static final IntField PARITY_8 = IntField.range(246, 251); + private static final IntField PARITY_9 = IntField.range(252, 257); + private static final IntField PARITY_10 = IntField.range(258, 263); + private static final IntField PARITY_11 = IntField.range(264, 269); + private static final IntField PARITY_12 = IntField.range(270, 275); + private static final IntField PARITY_13 = IntField.range(276, 281); + private static final IntField PARITY_14 = IntField.range(282, 287); + private static final IntField PARITY_15 = IntField.range(288, 293); + private static final IntField PARITY_16 = IntField.range(294, 299); + private static final IntField PARITY_17 = IntField.range(300, 305); + private static final IntField PARITY_18 = IntField.range(306, 311); + private static final IntField PARITY_19 = IntField.range(312, 317); private List mMacMessages; @@ -145,51 +149,51 @@ public List getMacMessages() // input[6] = 0; //Punctured // input[7] = 0; //Punctured // input[8] = 0; //Punctured - input[9] = getMessage().getInt(PARITY_19); - input[10] = getMessage().getInt(PARITY_18); - input[11] = getMessage().getInt(PARITY_17); - input[12] = getMessage().getInt(PARITY_16); - input[13] = getMessage().getInt(PARITY_15); - input[14] = getMessage().getInt(PARITY_14); - input[15] = getMessage().getInt(PARITY_13); - input[16] = getMessage().getInt(PARITY_12); - input[17] = getMessage().getInt(PARITY_11); - input[18] = getMessage().getInt(PARITY_10); - input[19] = getMessage().getInt(PARITY_9); - input[20] = getMessage().getInt(PARITY_8); - input[21] = getMessage().getInt(PARITY_7); - input[22] = getMessage().getInt(PARITY_6); - input[23] = getMessage().getInt(PARITY_5); - input[24] = getMessage().getInt(PARITY_4); - input[25] = getMessage().getInt(PARITY_3); - input[26] = getMessage().getInt(PARITY_2); - input[27] = getMessage().getInt(PARITY_1); - input[28] = getMessage().getInt(INFO_26); - input[29] = getMessage().getInt(INFO_25); - input[30] = getMessage().getInt(INFO_24); - input[27] = getMessage().getInt(INFO_23); - input[32] = getMessage().getInt(INFO_22); - input[33] = getMessage().getInt(INFO_21); - input[34] = getMessage().getInt(INFO_20); - input[35] = getMessage().getInt(INFO_19); - input[36] = getMessage().getInt(INFO_18); - input[37] = getMessage().getInt(INFO_17); - input[38] = getMessage().getInt(INFO_16); - input[39] = getMessage().getInt(INFO_15); - input[40] = getMessage().getInt(INFO_14); - input[41] = getMessage().getInt(INFO_13); - input[42] = getMessage().getInt(INFO_12); - input[43] = getMessage().getInt(INFO_11); - input[44] = getMessage().getInt(INFO_10); - input[45] = getMessage().getInt(INFO_9); - input[46] = getMessage().getInt(INFO_8); - input[47] = getMessage().getInt(INFO_7); - input[48] = getMessage().getInt(INFO_6); - input[49] = getMessage().getInt(INFO_5); - input[50] = getMessage().getInt(INFO_4); - input[51] = getMessage().getInt(INFO_3); - input[52] = getMessage().getInt(INFO_2); - input[53] = getMessage().getInt(INFO_1); + input[9] = getInt(PARITY_19); + input[10] = getInt(PARITY_18); + input[11] = getInt(PARITY_17); + input[12] = getInt(PARITY_16); + input[13] = getInt(PARITY_15); + input[14] = getInt(PARITY_14); + input[15] = getInt(PARITY_13); + input[16] = getInt(PARITY_12); + input[17] = getInt(PARITY_11); + input[18] = getInt(PARITY_10); + input[19] = getInt(PARITY_9); + input[20] = getInt(PARITY_8); + input[21] = getInt(PARITY_7); + input[22] = getInt(PARITY_6); + input[23] = getInt(PARITY_5); + input[24] = getInt(PARITY_4); + input[25] = getInt(PARITY_3); + input[26] = getInt(PARITY_2); + input[27] = getInt(PARITY_1); + input[28] = getInt(INFO_26); + input[29] = getInt(INFO_25); + input[30] = getInt(INFO_24); + input[31] = getInt(INFO_23); + input[32] = getInt(INFO_22); + input[33] = getInt(INFO_21); + input[34] = getInt(INFO_20); + input[35] = getInt(INFO_19); + input[36] = getInt(INFO_18); + input[37] = getInt(INFO_17); + input[38] = getInt(INFO_16); + input[39] = getInt(INFO_15); + input[40] = getInt(INFO_14); + input[41] = getInt(INFO_13); + input[42] = getInt(INFO_12); + input[43] = getInt(INFO_11); + input[44] = getInt(INFO_10); + input[45] = getInt(INFO_9); + input[46] = getInt(INFO_8); + input[47] = getInt(INFO_7); + input[48] = getInt(INFO_6); + input[49] = getInt(INFO_5); + input[50] = getInt(INFO_4); + input[51] = getInt(INFO_3); + input[52] = getInt(INFO_2); + input[53] = getInt(INFO_1); // input[54] = 0; //Shortened // input[55] = 0; //Shortened // input[56] = 0; //Shortened @@ -210,8 +214,8 @@ public List getMacMessages() } catch(Exception e) { - irrecoverableErrors = true; mLog.error("Error", e); + irrecoverableErrors = true; } CorrectedBinaryMessage binaryMessage = new CorrectedBinaryMessage(156); @@ -228,13 +232,12 @@ public List getMacMessages() pointer += 6; } - mMacMessages = MacMessageFactory.create(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp()); - if(irrecoverableErrors) { - mMacMessages.clear(); - MacMessage macMessage = new UnknownMacMessage(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp()); + MacMessage macMessage = new MacMessage(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp(), + new MacStructureFailedRS(binaryMessage, 0)); macMessage.setValid(false); + mMacMessages = new ArrayList<>(); mMacMessages.add(macMessage); } else @@ -249,6 +252,8 @@ public List getMacMessages() } } + mMacMessages = MacMessageFactory.create(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp(), + MAX_OCTET_INDEX); } return mMacMessages; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/LcchTimeslot.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/LcchTimeslot.java new file mode 100644 index 000000000..f0d9d098f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/LcchTimeslot.java @@ -0,0 +1,248 @@ +/* + * ***************************************************************************** + * 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.phase2.timeslot; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.edac.ReedSolomon_63_35_29_P25; +import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessageFactory; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureFailedRS; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Logical Control CHannel (LCCH) timeslot carrying an I-OECI message + */ +public class LcchTimeslot extends AbstractSignalingTimeslot +{ + private final static Logger mLog = LoggerFactory.getLogger(LcchTimeslot.class); + private static final int MAX_OCTET_INDEX = 152; //180-16-12 = message length minus CRC-16 checksum minus 12-bit NAC. + + private static final IntField INFO_1 = IntField.range(2, 7); + private static final IntField INFO_2 = IntField.range(8, 13); + private static final IntField INFO_3 = IntField.range(14, 19); + private static final IntField INFO_4 = IntField.range(20, 25); + private static final IntField INFO_5 = IntField.range(26, 31); + private static final IntField INFO_6 = IntField.range(32, 37); + private static final IntField INFO_7 = IntField.range(38, 43); + private static final IntField INFO_8 = IntField.range(44, 49); + private static final IntField INFO_9 = IntField.range(50, 55); + private static final IntField INFO_10 = IntField.range(56, 61); + private static final IntField INFO_11 = IntField.range(62, 67); + private static final IntField INFO_12 = IntField.range(68, 73); //Gap for duid 114-115 + private static final IntField INFO_13 = IntField.range(76, 81); + private static final IntField INFO_14 = IntField.range(82, 87); + private static final IntField INFO_15 = IntField.range(88, 93); + private static final IntField INFO_16 = IntField.range(94, 99); + private static final IntField INFO_17 = IntField.range(100, 105); + private static final IntField INFO_18 = IntField.range(106, 111); + private static final IntField INFO_19 = IntField.range(112, 117); + private static final IntField INFO_20 = IntField.range(118, 123); + private static final IntField INFO_21 = IntField.range(124, 129); + private static final IntField INFO_22 = IntField.range(130, 135); + private static final IntField INFO_23 = IntField.range(136, 141); + private static final IntField INFO_24 = IntField.range(142, 147); + private static final IntField INFO_25 = IntField.range(148, 153); + private static final IntField INFO_26 = IntField.range(154, 159); + private static final IntField INFO_27 = IntField.range(160, 165); + private static final IntField INFO_28 = IntField.range(166, 171); + private static final IntField INFO_29 = IntField.range(172, 177); + private static final IntField INFO_30 = IntField.range(178, 183); + private static final IntField PARITY_1 = IntField.range(184, 189); + private static final IntField PARITY_2 = IntField.range(190, 195); + private static final IntField PARITY_3 = IntField.range(196, 201); + private static final IntField PARITY_4 = IntField.range(202, 207); + private static final IntField PARITY_5 = IntField.range(208, 213); + private static final IntField PARITY_6 = IntField.range(214, 219); + private static final IntField PARITY_7 = IntField.range(220, 225); + private static final IntField PARITY_8 = IntField.range(226, 231); + private static final IntField PARITY_9 = IntField.range(232, 237); + private static final IntField PARITY_10 = IntField.range(238, 243); //Gap for duid 284-285 + private static final IntField PARITY_11 = IntField.range(246, 251); + private static final IntField PARITY_12 = IntField.range(252, 257); + private static final IntField PARITY_13 = IntField.range(258, 263); + private static final IntField PARITY_14 = IntField.range(264, 269); + private static final IntField PARITY_15 = IntField.range(270, 275); + private static final IntField PARITY_16 = IntField.range(276, 281); + private static final IntField PARITY_17 = IntField.range(282, 287); + private static final IntField PARITY_18 = IntField.range(288, 293); + private static final IntField PARITY_19 = IntField.range(294, 299); + private static final IntField PARITY_20 = IntField.range(300, 305); + private static final IntField PARITY_21 = IntField.range(306, 311); + private static final IntField PARITY_22 = IntField.range(312, 317); + + private List mMacMessages; + + /** + * Constructs an un-scrambled LCCH timeslot + * + * @param message containing 320 scrambled bits for the timeslot + * @param timeslot of the message + * @param timestamp of the message + */ + public LcchTimeslot(CorrectedBinaryMessage message, int timeslot, long timestamp) + { + super(message, DataUnitID.UNSCRAMBLED_LCCH, timeslot, timestamp); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("TS").append(getTimeslot()); + sb.append(" LC-UN"); + sb.append(" ").append(getMacMessages().toString()); + return sb.toString(); + } + + /** + * Information Outbound Encoded MAC Information (I-OEMI) message(s) carried by this timeslot + */ + @Override + public List getMacMessages() + { + if(mMacMessages == null) + { + int[] input = new int[63]; + int[] output = new int[63]; + +// input[0] = 0; //Punctured +// input[1] = 0; //Punctured +// input[2] = 0; //Punctured +// input[3] = 0; //Punctured +// input[4] = 0; //Punctured +// input[5] = 0; //Punctured + input[6] = getInt(PARITY_22); + input[7] = getInt(PARITY_21); + input[8] = getInt(PARITY_20); + input[9] = getInt(PARITY_19); + input[10] = getInt(PARITY_18); + input[11] = getInt(PARITY_17); + input[12] = getInt(PARITY_16); + input[13] = getInt(PARITY_15); + input[14] = getInt(PARITY_14); + input[15] = getInt(PARITY_13); + input[16] = getInt(PARITY_12); + input[17] = getInt(PARITY_11); + input[18] = getInt(PARITY_10); + input[19] = getInt(PARITY_9); + input[20] = getInt(PARITY_8); + input[21] = getInt(PARITY_7); + input[22] = getInt(PARITY_6); + input[23] = getInt(PARITY_5); + input[24] = getInt(PARITY_4); + input[25] = getInt(PARITY_3); + input[26] = getInt(PARITY_2); + input[27] = getInt(PARITY_1); + input[28] = getInt(INFO_30); + input[29] = getInt(INFO_29); + input[30] = getInt(INFO_28); + input[31] = getInt(INFO_27); + input[32] = getInt(INFO_26); + input[33] = getInt(INFO_25); + input[34] = getInt(INFO_24); + input[35] = getInt(INFO_23); + input[36] = getInt(INFO_22); + input[37] = getInt(INFO_21); + input[38] = getInt(INFO_20); + input[39] = getInt(INFO_19); + input[40] = getInt(INFO_18); + input[41] = getInt(INFO_17); + input[42] = getInt(INFO_16); + input[43] = getInt(INFO_15); + input[44] = getInt(INFO_14); + input[45] = getInt(INFO_13); + input[46] = getInt(INFO_12); + input[47] = getInt(INFO_11); + input[48] = getInt(INFO_10); + input[49] = getInt(INFO_9); + input[50] = getInt(INFO_8); + input[51] = getInt(INFO_7); + input[52] = getInt(INFO_6); + input[53] = getInt(INFO_5); + input[54] = getInt(INFO_4); + input[55] = getInt(INFO_3); + input[56] = getInt(INFO_2); + input[57] = getInt(INFO_1); +// input[58] = 0; //Shortened +// input[59] = 0; //Shortened +// input[60] = 0; //Shortened +// input[61] = 0; //Shortened +// input[62] = 0; //Shortened + + ReedSolomon_63_35_29_P25 reedSolomon_63_35_29 = new ReedSolomon_63_35_29_P25(); + + boolean irrecoverableErrors; + + try + { + irrecoverableErrors = reedSolomon_63_35_29.decode(input, output); + } + catch(Exception e) + { + mLog.error("Error", e); + irrecoverableErrors = true; + } + + CorrectedBinaryMessage binaryMessage = new CorrectedBinaryMessage(180); + + int pointer = 0; + + for(int x = 57; x >= 28; x--) + { + if(output[x] != -1) + { + binaryMessage.load(pointer, 6, output[x]); + } + + pointer += 6; + } + + if(irrecoverableErrors) + { + MacMessage macMessage = new MacMessage(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp(), + new MacStructureFailedRS(binaryMessage, 0)); + macMessage.setValid(false); + mMacMessages = new ArrayList<>(); + mMacMessages.add(macMessage); + } + else + { + //If we corrected any bit errors, update the original message with the bit error count + for(int x = 6; x <= 57; x++) + { + if(output[x] != input[x]) + { + binaryMessage.incrementCorrectedBitCount(Integer.bitCount((output[x] ^ input[x]))); + } + } + + mMacMessages = MacMessageFactory.create(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp(), + MAX_OCTET_INDEX); + } + } + + return mMacMessages; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/SacchTimeslot.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/SacchTimeslot.java index 45217ea93..697893008 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/SacchTimeslot.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/SacchTimeslot.java @@ -1,35 +1,33 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.timeslot; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.IntField; import io.github.dsheirer.edac.ReedSolomon_63_35_29_P25; import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessageFactory; -import io.github.dsheirer.module.decode.p25.phase2.message.mac.UnknownMacMessage; - +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureFailedRS; +import java.util.ArrayList; import java.util.List; /** @@ -37,58 +35,60 @@ */ public class SacchTimeslot extends AbstractSignalingTimeslot { - private static final int[] INFO_1 = {2, 3, 4, 5, 6, 7}; - private static final int[] INFO_2 = {8, 9, 10, 11, 12, 13}; - private static final int[] INFO_3 = {14, 15, 16, 17, 18, 19}; - private static final int[] INFO_4 = {20, 21, 22, 23, 24, 25}; - private static final int[] INFO_5 = {26, 27, 28, 29, 30, 31}; - private static final int[] INFO_6 = {32, 33, 34, 35, 36, 37}; - private static final int[] INFO_7 = {38, 39, 40, 41, 42, 43}; - private static final int[] INFO_8 = {44, 45, 46, 47, 48, 49}; - private static final int[] INFO_9 = {50, 51, 52, 53, 54, 55}; - private static final int[] INFO_10 = {56, 57, 58, 59, 60, 61}; - private static final int[] INFO_11 = {62, 63, 64, 65, 66, 67}; - private static final int[] INFO_12 = {68, 69, 70, 71, 72, 73}; //Gap for duid 114-115 - private static final int[] INFO_13 = {76, 77, 78, 79, 80, 81}; - private static final int[] INFO_14 = {82, 83, 84, 85, 86, 87}; - private static final int[] INFO_15 = {88, 89, 90, 91, 92, 93}; - private static final int[] INFO_16 = {94, 95, 96, 97, 98, 99}; - private static final int[] INFO_17 = {100, 101, 102, 103, 104, 105}; - private static final int[] INFO_18 = {106, 107, 108, 109, 110, 111}; - private static final int[] INFO_19 = {112, 113, 114, 115, 116, 117}; - private static final int[] INFO_20 = {118, 119, 120, 121, 122, 123}; - private static final int[] INFO_21 = {124, 125, 126, 127, 128, 129}; - private static final int[] INFO_22 = {130, 131, 132, 133, 134, 135}; - private static final int[] INFO_23 = {136, 137, 138, 139, 140, 141}; - private static final int[] INFO_24 = {142, 143, 144, 145, 146, 147}; - private static final int[] INFO_25 = {148, 149, 150, 151, 152, 153}; - private static final int[] INFO_26 = {154, 155, 156, 157, 158, 159}; - private static final int[] INFO_27 = {160, 161, 162, 163, 164, 165}; - private static final int[] INFO_28 = {166, 167, 168, 169, 170, 171}; - private static final int[] INFO_29 = {172, 173, 174, 175, 176, 177}; - private static final int[] INFO_30 = {178, 179, 180, 181, 182, 183}; - private static final int[] PARITY_1 = {184, 185, 186, 187, 188, 189}; - private static final int[] PARITY_2 = {190, 191, 192, 193, 194, 195}; - private static final int[] PARITY_3 = {196, 197, 198, 199, 200, 201}; - private static final int[] PARITY_4 = {202, 203, 204, 205, 206, 207}; - private static final int[] PARITY_5 = {208, 209, 210, 211, 212, 213}; - private static final int[] PARITY_6 = {214, 215, 216, 217, 218, 219}; - private static final int[] PARITY_7 = {220, 221, 222, 223, 224, 225}; - private static final int[] PARITY_8 = {226, 227, 228, 229, 230, 231}; - private static final int[] PARITY_9 = {232, 233, 234, 235, 236, 237}; - private static final int[] PARITY_10 = {238, 239, 240, 241, 242, 243}; //Gap for duid 284-285 - private static final int[] PARITY_11 = {246, 247, 248, 249, 250, 251}; - private static final int[] PARITY_12 = {252, 253, 254, 255, 256, 257}; - private static final int[] PARITY_13 = {258, 259, 260, 261, 262, 263}; - private static final int[] PARITY_14 = {264, 265, 266, 267, 268, 269}; - private static final int[] PARITY_15 = {270, 271, 272, 273, 274, 275}; - private static final int[] PARITY_16 = {276, 277, 278, 279, 280, 281}; - private static final int[] PARITY_17 = {282, 283, 284, 285, 286, 287}; - private static final int[] PARITY_18 = {288, 289, 290, 291, 292, 293}; - private static final int[] PARITY_19 = {294, 295, 296, 297, 298, 299}; - private static final int[] PARITY_20 = {300, 301, 302, 303, 304, 305}; - private static final int[] PARITY_21 = {306, 307, 308, 309, 310, 311}; - private static final int[] PARITY_22 = {312, 313, 314, 315, 316, 317}; + private static final int MAX_OCTET_INDEX = 168; //180-12 = message length minus CRC-12 checksum. + + private static final IntField INFO_1 = IntField.range(2, 7); + private static final IntField INFO_2 = IntField.range(8, 13); + private static final IntField INFO_3 = IntField.range(14, 19); + private static final IntField INFO_4 = IntField.range(20, 25); + private static final IntField INFO_5 = IntField.range(26, 31); + private static final IntField INFO_6 = IntField.range(32, 37); + private static final IntField INFO_7 = IntField.range(38, 43); + private static final IntField INFO_8 = IntField.range(44, 49); + private static final IntField INFO_9 = IntField.range(50, 55); + private static final IntField INFO_10 = IntField.range(56, 61); + private static final IntField INFO_11 = IntField.range(62, 67); + private static final IntField INFO_12 = IntField.range(68, 73); //Gap for duid 114-115 + private static final IntField INFO_13 = IntField.range(76, 81); + private static final IntField INFO_14 = IntField.range(82, 87); + private static final IntField INFO_15 = IntField.range(88, 93); + private static final IntField INFO_16 = IntField.range(94, 99); + private static final IntField INFO_17 = IntField.range(100, 105); + private static final IntField INFO_18 = IntField.range(106, 111); + private static final IntField INFO_19 = IntField.range(112, 117); + private static final IntField INFO_20 = IntField.range(118, 123); + private static final IntField INFO_21 = IntField.range(124, 129); + private static final IntField INFO_22 = IntField.range(130, 135); + private static final IntField INFO_23 = IntField.range(136, 141); + private static final IntField INFO_24 = IntField.range(142, 147); + private static final IntField INFO_25 = IntField.range(148, 153); + private static final IntField INFO_26 = IntField.range(154, 159); + private static final IntField INFO_27 = IntField.range(160, 165); + private static final IntField INFO_28 = IntField.range(166, 171); + private static final IntField INFO_29 = IntField.range(172, 177); + private static final IntField INFO_30 = IntField.range(178, 183); + private static final IntField PARITY_1 = IntField.range(184, 189); + private static final IntField PARITY_2 = IntField.range(190, 195); + private static final IntField PARITY_3 = IntField.range(196, 201); + private static final IntField PARITY_4 = IntField.range(202, 207); + private static final IntField PARITY_5 = IntField.range(208, 213); + private static final IntField PARITY_6 = IntField.range(214, 219); + private static final IntField PARITY_7 = IntField.range(220, 225); + private static final IntField PARITY_8 = IntField.range(226, 231); + private static final IntField PARITY_9 = IntField.range(232, 237); + private static final IntField PARITY_10 = IntField.range(238, 243); //Gap for duid 284-285 + private static final IntField PARITY_11 = IntField.range(246, 251); + private static final IntField PARITY_12 = IntField.range(252, 257); + private static final IntField PARITY_13 = IntField.range(258, 263); + private static final IntField PARITY_14 = IntField.range(264, 269); + private static final IntField PARITY_15 = IntField.range(270, 275); + private static final IntField PARITY_16 = IntField.range(276, 281); + private static final IntField PARITY_17 = IntField.range(282, 287); + private static final IntField PARITY_18 = IntField.range(288, 293); + private static final IntField PARITY_19 = IntField.range(294, 299); + private static final IntField PARITY_20 = IntField.range(300, 305); + private static final IntField PARITY_21 = IntField.range(306, 311); + private static final IntField PARITY_22 = IntField.range(312, 317); private List mMacMessages; @@ -156,58 +156,58 @@ public List getMacMessages() // input[3] = 0; //Punctured // input[4] = 0; //Punctured // input[5] = 0; //Punctured - input[6] = getMessage().getInt(PARITY_22); - input[7] = getMessage().getInt(PARITY_21); - input[8] = getMessage().getInt(PARITY_20); - input[9] = getMessage().getInt(PARITY_19); - input[10] = getMessage().getInt(PARITY_18); - input[11] = getMessage().getInt(PARITY_17); - input[12] = getMessage().getInt(PARITY_16); - input[13] = getMessage().getInt(PARITY_15); - input[14] = getMessage().getInt(PARITY_14); - input[15] = getMessage().getInt(PARITY_13); - input[16] = getMessage().getInt(PARITY_12); - input[17] = getMessage().getInt(PARITY_11); - input[18] = getMessage().getInt(PARITY_10); - input[19] = getMessage().getInt(PARITY_9); - input[20] = getMessage().getInt(PARITY_8); - input[21] = getMessage().getInt(PARITY_7); - input[22] = getMessage().getInt(PARITY_6); - input[23] = getMessage().getInt(PARITY_5); - input[24] = getMessage().getInt(PARITY_4); - input[25] = getMessage().getInt(PARITY_3); - input[26] = getMessage().getInt(PARITY_2); - input[27] = getMessage().getInt(PARITY_1); - input[28] = getMessage().getInt(INFO_30); - input[29] = getMessage().getInt(INFO_29); - input[30] = getMessage().getInt(INFO_28); - input[31] = getMessage().getInt(INFO_27); - input[32] = getMessage().getInt(INFO_26); - input[33] = getMessage().getInt(INFO_25); - input[34] = getMessage().getInt(INFO_24); - input[35] = getMessage().getInt(INFO_23); - input[36] = getMessage().getInt(INFO_22); - input[37] = getMessage().getInt(INFO_21); - input[38] = getMessage().getInt(INFO_20); - input[39] = getMessage().getInt(INFO_19); - input[40] = getMessage().getInt(INFO_18); - input[41] = getMessage().getInt(INFO_17); - input[42] = getMessage().getInt(INFO_16); - input[43] = getMessage().getInt(INFO_15); - input[44] = getMessage().getInt(INFO_14); - input[45] = getMessage().getInt(INFO_13); - input[46] = getMessage().getInt(INFO_12); - input[47] = getMessage().getInt(INFO_11); - input[48] = getMessage().getInt(INFO_10); - input[49] = getMessage().getInt(INFO_9); - input[50] = getMessage().getInt(INFO_8); - input[51] = getMessage().getInt(INFO_7); - input[52] = getMessage().getInt(INFO_6); - input[53] = getMessage().getInt(INFO_5); - input[54] = getMessage().getInt(INFO_4); - input[55] = getMessage().getInt(INFO_3); - input[56] = getMessage().getInt(INFO_2); - input[57] = getMessage().getInt(INFO_1); + input[6] = getInt(PARITY_22); + input[7] = getInt(PARITY_21); + input[8] = getInt(PARITY_20); + input[9] = getInt(PARITY_19); + input[10] = getInt(PARITY_18); + input[11] = getInt(PARITY_17); + input[12] = getInt(PARITY_16); + input[13] = getInt(PARITY_15); + input[14] = getInt(PARITY_14); + input[15] = getInt(PARITY_13); + input[16] = getInt(PARITY_12); + input[17] = getInt(PARITY_11); + input[18] = getInt(PARITY_10); + input[19] = getInt(PARITY_9); + input[20] = getInt(PARITY_8); + input[21] = getInt(PARITY_7); + input[22] = getInt(PARITY_6); + input[23] = getInt(PARITY_5); + input[24] = getInt(PARITY_4); + input[25] = getInt(PARITY_3); + input[26] = getInt(PARITY_2); + input[27] = getInt(PARITY_1); + input[28] = getInt(INFO_30); + input[29] = getInt(INFO_29); + input[30] = getInt(INFO_28); + input[31] = getInt(INFO_27); + input[32] = getInt(INFO_26); + input[33] = getInt(INFO_25); + input[34] = getInt(INFO_24); + input[35] = getInt(INFO_23); + input[36] = getInt(INFO_22); + input[37] = getInt(INFO_21); + input[38] = getInt(INFO_20); + input[39] = getInt(INFO_19); + input[40] = getInt(INFO_18); + input[41] = getInt(INFO_17); + input[42] = getInt(INFO_16); + input[43] = getInt(INFO_15); + input[44] = getInt(INFO_14); + input[45] = getInt(INFO_13); + input[46] = getInt(INFO_12); + input[47] = getInt(INFO_11); + input[48] = getInt(INFO_10); + input[49] = getInt(INFO_9); + input[50] = getInt(INFO_8); + input[51] = getInt(INFO_7); + input[52] = getInt(INFO_6); + input[53] = getInt(INFO_5); + input[54] = getInt(INFO_4); + input[55] = getInt(INFO_3); + input[56] = getInt(INFO_2); + input[57] = getInt(INFO_1); // input[58] = 0; //Shortened // input[59] = 0; //Shortened // input[60] = 0; //Shortened @@ -224,6 +224,7 @@ public List getMacMessages() } catch(Exception e) { + e.printStackTrace(); irrecoverableErrors = true; } @@ -241,27 +242,29 @@ public List getMacMessages() pointer += 6; } - mMacMessages = MacMessageFactory.create(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp()); if(irrecoverableErrors) { - mMacMessages.clear(); - MacMessage macMessage = new UnknownMacMessage(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp()); + MacMessage macMessage = new MacMessage(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp(), + new MacStructureFailedRS(binaryMessage, 0)); macMessage.setValid(false); + mMacMessages = new ArrayList<>(); mMacMessages.add(macMessage); } else { //If we corrected any bit errors, update the original message with the bit error count - for(int x = 0; x <= 57; x++) + for(int x = 6; x <= 57; x++) { if(output[x] != input[x]) { binaryMessage.incrementCorrectedBitCount(Integer.bitCount((output[x] ^ input[x]))); } } - } + mMacMessages = MacMessageFactory.create(getTimeslot(), getDataUnitID(), binaryMessage, getTimestamp(), 99); + + } } return mMacMessages; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/ScramblingSequence.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/ScramblingSequence.java index c5f0b272a..8a91897ff 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/ScramblingSequence.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/ScramblingSequence.java @@ -1,34 +1,30 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.phase2.timeslot; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * APCO-25 Phase II scrambling sequence utility that provides scrambling sequence snippets for each of the 12 timeslots @@ -55,19 +51,21 @@ public ScramblingSequence() /** * Updates this scrambling sequence with the specified seed parameters */ - public void update(ScrambleParameters parameters) + public boolean update(ScrambleParameters parameters) { if(parameters != null) { - update(parameters.getWACN(), parameters.getSystem(), parameters.getNAC()); + return update(parameters.getWACN(), parameters.getSystem(), parameters.getNAC()); } + + return false; } /** * Updates this scrambling sequence with the specified parameters from the Network Broadcast Status message and * generates 12 x 320-bit scrambling sequences for each of the superframe's 12 timeslots. */ - public void update(int wacn, int system, int nac) + public boolean update(int wacn, int system, int nac) { if(!mShiftRegister.isCurrent(wacn, system, nac)) { @@ -81,7 +79,12 @@ public void update(int wacn, int system, int nac) { mScramblingSegments.add(scramblingSequence.getSubMessage(x, x + 320)); } + + //Return true to indicate that the sequence was updated + return true; } + + return false; } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/Timeslot.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/Timeslot.java index 867cdf251..7662fe376 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/Timeslot.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/Timeslot.java @@ -1,29 +1,27 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.timeslot; import io.github.dsheirer.bits.BinaryMessage; import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.bits.FragmentedIntField; import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID; import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message; @@ -32,10 +30,8 @@ */ public abstract class Timeslot extends P25P2Message { - public static final int[] DATA_UNIT_ID = {0,1,74,75,244,245,318,319}; - private CorrectedBinaryMessage mMessage; + private static final FragmentedIntField DATA_UNIT_ID = FragmentedIntField.of(0,1,74,75,244,245,318,319); private DataUnitID mDataUnitID; - private int mTimeslot; /** * Constructs a scrambled timeslot instance and automatically descrambles the transmitted bits. @@ -61,15 +57,8 @@ protected Timeslot(CorrectedBinaryMessage message, DataUnitID dataUnitID, Binary */ protected Timeslot(CorrectedBinaryMessage message, DataUnitID dataUnitID, int timeslot, long timestamp) { - super(timestamp); - mMessage = message; + super(message, 0, timeslot, timestamp); mDataUnitID = dataUnitID; - mTimeslot = timeslot; - } - - protected CorrectedBinaryMessage getMessage() - { - return mMessage; } public DataUnitID getDataUnitID() @@ -77,15 +66,6 @@ public DataUnitID getDataUnitID() return mDataUnitID; } - /** - * Timeslot - * @return channel number (either Channel 0 or Channel 1) - */ - public int getTimeslot() - { - return mTimeslot; - } - /** * Lookup the Data Unit ID for this timeslot * @param message containing a 320-bit timeslot frame with interleaved 8-bit duid value. diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/TimeslotFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/TimeslotFactory.java index 14cd137e2..0a72226ba 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/TimeslotFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/TimeslotFactory.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.timeslot; @@ -59,9 +56,10 @@ public static Timeslot getTimeslot(CorrectedBinaryMessage message, BinaryMessage return new FacchTimeslot(message, timeslot, timestamp); case UNSCRAMBLED_SACCH: return new SacchTimeslot(message, timeslot, timestamp); - case UNKNOWN: + case UNSCRAMBLED_LCCH: + return new LcchTimeslot(message, timeslot, timestamp); default: - return new UnknownTimeslot(message, timeslot, timestamp); + return new UnknownTimeslot(message, timeslot, timestamp, dataUnitID); } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/UnknownTimeslot.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/UnknownTimeslot.java index 2c3dd0ad5..1f89b935a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/UnknownTimeslot.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/timeslot/UnknownTimeslot.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.phase2.timeslot; @@ -25,15 +22,26 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID; - import java.util.Collections; import java.util.List; +/** + * Unknown timeslot - usually happens when we decode the Data Unit ID incorrectly. + */ public class UnknownTimeslot extends Timeslot { - public UnknownTimeslot(CorrectedBinaryMessage message, int timeslot, long timestamp) + public UnknownTimeslot(CorrectedBinaryMessage message, int timeslot, long timestamp, DataUnitID dataUnitID) + { + super(message, dataUnitID, timeslot, timestamp); + } + + @Override + public String toString() { - super(message, DataUnitID.VOICE_2, timeslot, timestamp); + StringBuilder sb = new StringBuilder(); + sb.append("TS").append(getTimeslot()); + sb.append(" UNKNOWN TIMESLOT - DATA UNIT ID: ").append(getDataUnitID()); + return sb.toString(); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/MotorolaLinkControlWord.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ArgumentType.java similarity index 59% rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/MotorolaLinkControlWord.java rename to src/main/java/io/github/dsheirer/module/decode/p25/reference/ArgumentType.java index 60ec376d1..06bb4420b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/MotorolaLinkControlWord.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ArgumentType.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,23 +14,18 @@ * * 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.lc.motorola; +package io.github.dsheirer.module.decode.p25.reference; -import io.github.dsheirer.bits.BinaryMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; - -public abstract class MotorolaLinkControlWord extends LinkControlWord +/** + * Enumeration of argument types for the extended function. + */ +public enum ArgumentType { - /** - * Constructs a Link Control Word from the binary message sequence. - * - * @param message - */ - public MotorolaLinkControlWord(BinaryMessage message) - { - super(message); - } + SOURCE_RADIO, + TARGET_RADIO, + TALKGROUP, + NONE; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataServiceOptions.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataServiceOptions.java index 06d7548ea..28a26680f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataServiceOptions.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DataServiceOptions.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.reference; @@ -42,18 +41,16 @@ public String toString() StringBuilder sb = new StringBuilder(); sb.append("NSAPI:").append(getNSAPI()); - if(isEncrypted()) - { - sb.append(" ENCRYPTED"); - } - if(isEmergency()) { sb.append(" EMERGENCY"); } - - sb.append(" ").append(getSessionMode()); - + if(isEncrypted()) + { + sb.append(" ENCRYPTED"); + } + sb.append(" ").append(getDuplex()).append(" DUPLEX"); + sb.append(" ").append(getSessionMode()).append(" MODE"); return sb.toString(); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/DenyReason.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DenyReason.java index 57c8c1d0a..6b347f1f7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/DenyReason.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/DenyReason.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.reference; @@ -46,15 +45,48 @@ public enum DenyReason DUPLEX_SERVICE_OPTION_NOT_VALID(0xF2), CIRCUIT_OR_PACKET_MODE_OPTION_NOT_VALID(0xF3), SYSTEM_DOES_NOT_SUPPORT_SERVICE(0xFF), + SECURE_REQUEST_ON_CLEAR_SUPERGROUP(0x00), //Custom: Motorola & L3Harris + CLEAR_REQUEST_ON_SECURE_SUPERGROUP(0x01), //Custom: Motorola & L3Harris UNKNOWN(-1); private int mCode; - private DenyReason(int code) + DenyReason(int code) { mCode = code; } + /** + * Utility method to lookup the entry from the code that is custom for a vendor. + * @param code to lookup + * @param vendor identity + * @return matching enum entry or UNKNOWN. + */ + public static DenyReason fromCustomCode(int code, Vendor vendor) + { + switch(vendor) + { + case MOTOROLA: + case HARRIS: + if(code == 0x00) + { + return SECURE_REQUEST_ON_CLEAR_SUPERGROUP; + } + else if(code == 0x01) + { + return CLEAR_REQUEST_ON_SECURE_SUPERGROUP; + } + //Deliberate fall-through + default: + return fromCode(code); + } + } + + /** + * Utility method to lookup the entry from the code. + * @param code to lookup + * @return matching enum entry or UNKNOWN. + */ public static DenyReason fromCode(int code) { if(code == 0x10) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Encryption.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Encryption.java index 7069f634d..1e1b5dc4a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Encryption.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Encryption.java @@ -1,27 +1,29 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 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 - * * ***************************************************************************** + * 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.reference; +/** + * APCO25 Encryption Algorithm IDs. + * + * Note: values are from a variety of places, including from the OP-25 project. + */ public enum Encryption { ACCORDION_3(0x00, "ACCORDIAN 3"), @@ -29,17 +31,36 @@ public enum Encryption FIREFLY_TYPE1(0x02, "FIREFLY"), MAYFLY_TYPE1(0x03, "MAYFLY"), SAVILLE(0x04, "SAVILLE"), + MOTOROLA_PADSTONE(0x05, "MOTOROLA PADSTONE"), //from OP25 BATON_AUTO_ODD(0x41, "BATON AUTO ODD"), UNENCRYPTED(0x80, "UNENCRYPTED"), DES_OFB(0x81, "DES OFB"), - TRIPLE_DES_2_KEY(0x82, "TRIPLE DES 2"), - TRIPLE_DES_3_KEY(0x83, "TRIPLE DES 3"), + TRIPLE_DES_2_KEY(0x82, "2-KEY TRIPLE DES"), + TRIPLE_DES_3_KEY(0x83, "3-KEY TRIPLE DES"), AES_256(0x84, "AES-256"), - AES_CBC(0x85, "AES-CBC"), - DES_XL(0x9F, "DES-XL"), /* Motorola Proprietary */ - DVI_XL(0xA0, "DVI-XL"), /* Motorola Proprietary */ - DVP_XL(0xA1, "DVP-XL"), /* Motorola Proprietary */ - ADP(0xAA, "ADP"), + AES_128(0x85, "AES-128"), + AES_CBC(0x88, "AES-CBC"), //from OP25 + + //Below from OP25 ... + AES_128_OFB(0x89, "AES-128-OFB"), + DES_XL(0x9F, "MOTOROLA DES-XL"), + DVI_XL(0xA0, "MOTOROLA DVI-XL"), + DVP_XL(0xA1, "MOTOROLA DVP-XL"), + DVP_SPFL(0xA2, "MOTOROLA DVP-SPFL"), + HAYSTACK(0xA3, "MOTOROLA HAYSTACK"), + MOTOROLA_A4(0xA4, "MOTOROLA UNKNOWN A4"), + MOTOROLA_A5(0xA5, "MOTOROLA UNKNOWN A5"), + MOTOROLA_A6(0xA6, "MOTOROLA UNKNOWN A6"), + MOTOROLA_A7(0xA7, "MOTOROLA UNKNOWN A7"), + MOTOROLA_A8(0xA8, "MOTOROLA UNKNOWN A8"), + MOTOROLA_A9(0xA9, "MOTOROLA UNKNOWN A9"), + MOTOROLA_ADP(0xAA, "MOTOROLA ADP 40-BIT RC4"), + MOTOROLA_AB(0xAB, "MOTOROLA CFX-256"), + MOTOROLA_AC(0xAC, "MOTOROLA UNKNOWN AC"), + MOTOROLA_AD(0xAD, "MOTOROLA UNKNOWN AD"), + MOTOROLA_AE(0xAE, "MOTOROLA UNKNOWN AE"), + MOTOROLA_AF(0xAF, "MOTOROLA AES-256-GCM"), + MOTOROLA_B0(0xB0, "MOTOROLA DVP B0"), UNKNOWN(-1, "UNKNOWN"); private int mValue; @@ -71,6 +92,8 @@ public static Encryption fromValue(int value) return MAYFLY_TYPE1; case 0x04: return SAVILLE; + case 0x05: + return MOTOROLA_PADSTONE; case 0x41: return BATON_AUTO_ODD; case 0x80: @@ -84,18 +107,45 @@ public static Encryption fromValue(int value) case 0x84: return AES_256; case 0x85: - return AES_CBC; + return AES_128; case 0x9F: return DES_XL; case 0xA0: return DVI_XL; case 0xA1: return DVP_XL; + case 0xA2: + return DVP_SPFL; + case 0xA3: + return HAYSTACK; + case 0xA4: + return MOTOROLA_A4; + case 0xA5: + return MOTOROLA_A5; + case 0xA6: + return MOTOROLA_A6; + case 0xA7: + return MOTOROLA_A7; + case 0xA8: + return MOTOROLA_A8; + case 0xA9: + return MOTOROLA_A9; case 0xAA: - return ADP; + return MOTOROLA_ADP; + case 0xAB: + return MOTOROLA_AB; + case 0xAC: + return MOTOROLA_AC; + case 0xAD: + return MOTOROLA_AD; + case 0xAE: + return MOTOROLA_AE; + case 0xAF: + return MOTOROLA_AF; + case 0xB0: + return MOTOROLA_B0; default: return UNKNOWN; } - } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ExtendedFunction.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ExtendedFunction.java index 77ef2f2ca..1cfa6ae7d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ExtendedFunction.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ExtendedFunction.java @@ -1,29 +1,49 @@ +/* + * ***************************************************************************** + * 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.reference; public enum ExtendedFunction { - RADIO_CHECK(0x0000, "RADIO CHECK"), - RADIO_DETACH(0x007D, "RADIO DETACH"), - RADIO_UNINHIBIT(0x007E, "RADIO UNINHIBIT"), - RADIO_INHIBIT(0x007F, "RADIO INHIBIT"), - RADIO_CHECK_ACK(0x0080, "RADIO CHECK ACK"), - RADIO_DETACH_ACK(0x00FD, "RADIO DETACH ACK"), - RADIO_UNINHIBIT_ACK(0x00FE, "RADIO UNINHIBIT ACK"), - RADIO_INHIBIT_ACK(0x00FF, "RADIO INHIBIT ACK"), - - GROUP_CONTROL_COMMAND(0x0100, "GROUP CONTROL COMMAND"), - UNIT_DYNAMIC_COMMAND(0x0200, "UNIT DYNAMIC COMMAND"), - GROUP_DYNAMIC_COMMAND(0x0300, "GROUP DYNAMIC COMMAND"), - - UNKNOWN(-1, "UNKNOWN"); + RADIO_CHECK(0x0000, "RADIO CHECK FROM RADIO:", ArgumentType.SOURCE_RADIO), + RADIO_DETACH(0x007D, "RADIO DETACH FROM RADIO:", ArgumentType.SOURCE_RADIO ), + RADIO_UNINHIBIT(0x007E, "RADIO UNINHIBIT FROM RADIO:", ArgumentType.SOURCE_RADIO), + RADIO_INHIBIT(0x007F, "RADIO INHIBIT FROM RADIO:", ArgumentType.SOURCE_RADIO), + RADIO_CHECK_ACK(0x0080, "RADIO CHECK ACK TO RADIO:", ArgumentType.TARGET_RADIO), + RADIO_DETACH_ACK(0x00FD, "RADIO DETACH ACK TO RADIO:", ArgumentType.TARGET_RADIO), + RADIO_UNINHIBIT_ACK(0x00FE, "RADIO UNINHIBIT ACK TO RADIO:", ArgumentType.TARGET_RADIO), + RADIO_INHIBIT_ACK(0x00FF, "RADIO INHIBIT ACK TO RADIO:", ArgumentType.TARGET_RADIO), + GROUP_REGROUP_CREATE_SUPERGROUP(0x0200, "GROUP REGROUP CREATE SUPERGROUP:", ArgumentType.TALKGROUP), + GROUP_REGROUP_CANCEL_SUPERGROUP(0x0201, "GROUP REGROUP CANCEL SUPERGROUP", ArgumentType.NONE), + GROUP_REGROUP_ACK_CREATE_SUPERGROUP(0x0280, "GROUP REGROUP ACK CREATE SUPERGROUP:", ArgumentType.TALKGROUP), + GROUP_REGROUP_ACK_CANCEL_SUPERGROUP(0x0281, "GROUP REGROUP ACK CANCEL SUPERGROUP:", ArgumentType.TALKGROUP), + UNKNOWN(-1, "UNKNOWN", ArgumentType.NONE); private int mFunction; private String mLabel; + private ArgumentType mArgumentType; - ExtendedFunction(int function, String label) + ExtendedFunction(int function, String label, ArgumentType argumentType) { mFunction = function; mLabel = label; + mArgumentType = argumentType; } public String getLabel() @@ -31,6 +51,14 @@ public String getLabel() return mLabel; } + /** + * Indicates the type of address carried in the argument field. + */ + public ArgumentType getArgumentType() + { + return mArgumentType; + } + @Override public String toString() { @@ -57,19 +85,14 @@ public static ExtendedFunction fromValue(int function) return RADIO_UNINHIBIT_ACK; case 0x00FF: return RADIO_INHIBIT_ACK; - } - - if((function & 0x0100) == 0x0100) - { - return GROUP_CONTROL_COMMAND; - } - if((function & 0x0200) == 0x0200) - { - return UNIT_DYNAMIC_COMMAND; - } - if((function & 0x0300) == 0x0300) - { - return GROUP_DYNAMIC_COMMAND; + case 0x0200: + return GROUP_REGROUP_CREATE_SUPERGROUP; + case 0x0201: + return GROUP_REGROUP_CANCEL_SUPERGROUP; + case 0x0280: + return GROUP_REGROUP_ACK_CREATE_SUPERGROUP; + case 0x0281: + return GROUP_REGROUP_ACK_CANCEL_SUPERGROUP; } return UNKNOWN; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Response.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Response.java index 1dddcf3d7..3082c3aa7 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Response.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Response.java @@ -1,10 +1,29 @@ +/* + * ***************************************************************************** + * 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.reference; public enum Response { - ACCEPT, - FAIL, - DENY, + ACCEPTED, + FAILED, + DENIED, REFUSED, UNKNOWN; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Service.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Service.java index 0bc82cf13..b4e7a7ecc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/Service.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/Service.java @@ -1,66 +1,104 @@ +/* + * ***************************************************************************** + * 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.reference; import java.util.ArrayList; import java.util.List; +/** + * APCO-25 Services enumeration + */ public enum Service { - EXTENDED_SERVICES( 0x800000l ), - EXTENDED_SERVICES_EXTENSION( 0x400000l ), + EXTENDED_SERVICES(0x800000), + EXTENDED_SERVICES_EXTENSION(0x400000), /* NORMAL SERVICES */ - NETWORK_ACTIVE( 0x200000l ), - RESERVED_4( 0x100000l ), - GROUP_VOICE( 0x080000l ), - INDIVIDUAL_VOICE( 0x040000l ), - PSTN_TO_UNIT_VOICE( 0x020000l ), - UNIT_TO_PSTN_VOICE( 0x010000l ), - RESERVED_9( 0x008000l ), - GROUP_DATA( 0x004000l ), - INDIVIDUAL_DATA( 0x002000l ), - RESERVED_12( 0x001000l ), - UNIT_REGISTRATION( 0x000800l ), - GROUP_AFFILIATION( 0x000400l ), - GROUP_AFFILIATION_QUERY( 0x000200l ), - AUTHENTICATION( 0x000100l ), - ENCRYPTION_SETTINGS( 0x000080l ), - USER_STATUS( 0x000040l ), - USER_MESSAGE( 0x000020l ), - UNIT_STATUS( 0x000010l ), - USER_STATUS_QUERY( 0x000008l ), - UNIT_STATUS_QUERY( 0x000004l ), - UNIT_PAGE( 0x000002l ), - EMERGENCY_ALARM( 0x000001l ), - UNKNOWN( 0x0l ); - - private long mCode; - - private Service( long code ) + NETWORK_ACTIVE(0x200000), + RESERVED_4(0x100000), + GROUP_VOICE(0x080000), + INDIVIDUAL_VOICE(0x040000), + PSTN_TO_UNIT_VOICE(0x020000), + UNIT_TO_PSTN_VOICE(0x010000), + RESERVED_9(0x008000), + GROUP_DATA(0x004000), + INDIVIDUAL_DATA(0x002000), + RESERVED_12(0x001000), + UNIT_REGISTRATION(0x000800), + GROUP_AFFILIATION(0x000400), + GROUP_AFFILIATION_QUERY(0x000200), + AUTHENTICATION(0x000100), + ENCRYPTION_SETTINGS(0x000080), + USER_STATUS(0x000040), + USER_MESSAGE(0x000020), + UNIT_STATUS(0x000010), + USER_STATUS_QUERY(0x000008), + UNIT_STATUS_QUERY(0x000004), + CALL_ALERT(0x000002), + EMERGENCY_ALARM(0x000001), + UNKNOWN(0x0); + + private int mCode; + + /** + * Constructs an instance + * @param code that is a bitmap of service entry values + */ + Service(int code) { mCode = code; } - - public long getCode() + + /** + * Bitmap code + */ + public int getCode() { return mCode; } - public static boolean isSupported( Service service, long serviceBitmap ) + /** + * Indicates if the flag for the specified service is set in the bitmap. + * @param service to check + * @param serviceBitmap containing set flag values. + * @return true if the service is set in the bitmap. + */ + public static boolean isSupported(Service service, int serviceBitmap) { return ( service.getCode() & serviceBitmap ) == service.getCode(); } - - public static List getServices( long serviceBitmap ) + + /** + * List of service indicated by the bitmap. + */ + public static List getServices(int serviceBitmap) { - List services = new ArrayList(); - - for( Service service: values() ) + List services = new ArrayList<>(); + + for(Service service : values()) { - if( isSupported( service, serviceBitmap ) ) + if(isSupported(service, serviceBitmap)) { - if( service != Service.UNKNOWN ) + if(service != Service.UNKNOWN) { - services.add( service ); + services.add(service); } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceAccessPoint.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceAccessPoint.java index 9c6e33d97..640cfd61e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceAccessPoint.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceAccessPoint.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,7 +14,7 @@ * * 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.reference; @@ -37,7 +36,7 @@ public enum ServiceAccessPoint SAP_12("12", 12), SAP_13("13", 13), SAP_14("14", 14), - SAP_15("15", 15), + PACKET_DATA_SCAN_PREAMBLE("PACKET DATA SCAN PREAMBLE", 15), SAP_16("16", 16), SAP_17("17", 17), SAP_18("18", 18), @@ -51,9 +50,9 @@ public enum ServiceAccessPoint SAP_26("26", 26), SAP_27("27", 27), SAP_28("28", 28), - SAP_29("29", 29), + PACKET_DATA_ENCRYPTION_SUPPORT("PACKET DATA ENCRYPTION SUPPORT", 29), SAP_30("30", 30), - EXTENDED_ADDRESS("EXTENDED ADDRESS", 31), + EXTENDED_ADDRESS("EXTENDED ADDRESS FOR SYMMETRIC ADDRESSING", 31), REGISTRATION_AND_AUTHORIZATION("REGISTRATION AND AUTHORIZATION", 32), CHANNEL_REASSIGNMENT("CHANNEL REASSIGNMENT", 33), SYSTEM_CONFIGURATION("SYSTEM CONFIGURATION", 34), @@ -70,7 +69,7 @@ public enum ServiceAccessPoint SAP_45("45", 45), SAP_46("46", 46), SAP_47("47", 47), - SAP_48("48", 48), + LOCATION_SERVICE("LOCATION SERVICE", 48), SAP_49("49", 49), SAP_50("50", 50), SAP_51("51", 51), diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceOptions.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceOptions.java index 4e1ee1d44..6a98aec33 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceOptions.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/ServiceOptions.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2018 Dennis Sheirer + * ***************************************************************************** + * 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 @@ -15,23 +14,26 @@ * * 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.reference; /** - * Channel allocation service options + * Base channel allocation service options */ -public class ServiceOptions +public abstract class ServiceOptions { - private static final int EMERGENCY_FLAG = 0x80; - private static final int ENCRYPTION_FLAG = 0x40; - private static final int DUPLEX = 0x20; - private static final int SESSION_MODE = 0x10; - + protected static final int EMERGENCY_FLAG = 0x80; + protected static final int ENCRYPTION_FLAG = 0x40; + protected static final int DUPLEX = 0x20; + protected static final int SESSION_MODE = 0x10; protected int mServiceOptions; + /** + * Constructs an instance + * @param serviceOptions + */ public ServiceOptions(int serviceOptions) { mServiceOptions = serviceOptions; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/SiteFlags.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/SiteFlags.java new file mode 100644 index 000000000..af2dded3d --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/SiteFlags.java @@ -0,0 +1,88 @@ +/* + * ***************************************************************************** + * 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.reference; + +import java.util.ArrayList; +import java.util.List; + +/** + * Site flags (C, F, V, A) + */ +public class SiteFlags +{ + private static final int CONVENTIONAL_CHANNEL_FLAG = 0x08; + private static final int SITE_FAILURE_FLAG = 0x04; + private static final int VALID_INFORMATION_FLAG = 0x02; + private static final int ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG = 0x01; + + private int mFlags; + + public SiteFlags(int flags) + { + mFlags = flags; + } + + public List getFlags() + { + List flags = new ArrayList<>(); + + if(hasFlag(CONVENTIONAL_CHANNEL_FLAG)) + { + flags.add("CONVENTIONAL CHANNEL"); + } + + if(hasFlag(SITE_FAILURE_FLAG)) + { + flags.add("FAILURE CONDITION"); + } + + if(hasFlag(VALID_INFORMATION_FLAG)) + { + flags.add("VALID INFORMATION"); + } + + if(hasFlag(ACTIVE_NETWORK_CONNECTION_TO_RFSS_CONTROLLER_FLAG)) + { + flags.add("ACTIVE RFSS CONNECTION"); + } + + return flags; + } + + private boolean hasFlag(int flag) + { + return (mFlags & flag) == flag; + } + + @Override + public String toString() + { + return getFlags().toString(); + } + + /** + * Utility method to create flags instance. + * @param flags number value. + * @return instance + */ + public static SiteFlags create(int flags) + { + return new SiteFlags(flags); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java index 6761dbcee..74b2d79db 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java @@ -1,18 +1,21 @@ -/******************************************************************************* - * sdr-trunk - * Copyright (C) 2014-2018 Dennis Sheirer +/* + * ***************************************************************************** + * 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 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. + * 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 - * - ******************************************************************************/ + * 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.reference; public class VoiceServiceOptions extends ServiceOptions @@ -61,4 +64,20 @@ public String toString() return sb.toString(); } + + /** + * Creates an instance with the encryption flag set. + */ + public static VoiceServiceOptions createEncrypted() + { + return new VoiceServiceOptions(ENCRYPTION_FLAG); + } + + /** + * Creates an instance where the encryption flag is not set. + */ + public static VoiceServiceOptions createUnencrypted() + { + return new VoiceServiceOptions(0); + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/traffic/TrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/traffic/TrafficChannelManager.java index d53df5132..0adf1b071 100644 --- a/src/main/java/io/github/dsheirer/module/decode/traffic/TrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/traffic/TrafficChannelManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -19,6 +19,7 @@ package io.github.dsheirer.module.decode.traffic; +import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.module.Module; /** @@ -26,4 +27,44 @@ */ public abstract class TrafficChannelManager extends Module { + private long mCurrentControlFrequency; + + /** + * Constructs an instance. + */ + public TrafficChannelManager() + { + } + + /** + * Current control frequency + * @return frequency in hertz + */ + protected long getCurrentControlFrequency() + { + return mCurrentControlFrequency; + } + + /** + * Sets the current control frequency. + * @param frequency that is now the control frequency + * @param parentChannel channel configuration + */ + public void setCurrentControlFrequency(long frequency, Channel parentChannel) + { + long previous = mCurrentControlFrequency; + mCurrentControlFrequency = frequency; + processControlFrequencyUpdate(previous, frequency, parentChannel); + } + + /** + * Subclass implementation to receive notification that the control channel frequency has changed when the source + * is set for multiple frequencies, or in the case of DMR when the REST channel changes. Subclass should remove + * any traffic channels currently allocated against the new control frequency. + * + * @param previous frequency for the control channel (to remove from allocated channels) + * @param current frequency for the control channel (to add to allocated channels) + * @param channel for the current control channel + */ + protected abstract void processControlFrequencyUpdate(long previous, long current, Channel channel); } diff --git a/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java b/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java index b23f8a2f1..dedf58226 100644 --- a/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java +++ b/src/main/java/io/github/dsheirer/module/log/DecodeEventLogger.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -66,7 +66,7 @@ public DecodeEventLogger(AliasModel aliasModel, Path logDirectory, String fileNa @Override public void receive(IDecodeEvent decodeEvent) { - write(toCSV(decodeEvent)); + write(toCSV(decodeEvent)); } @Override @@ -88,7 +88,7 @@ public void reset() public static String getCSVHeader() { - return "TIMESTAMP,DURATION_MS,PROTOCOL,EVENT,FROM,TO,CHANNEL_NUMBER,FREQUENCY,TIMESLOT,DETAILS"; + return "TIMESTAMP,DURATION_MS,PROTOCOL,EVENT,FROM,TO,CHANNEL_NUMBER,FREQUENCY,TIMESLOT,DETAILS,EVENT_ID"; } private String toCSV(IDecodeEvent event) @@ -137,18 +137,25 @@ private String toCSV(IDecodeEvent event) IChannelDescriptor descriptor = event.getChannelDescriptor(); cells.add(descriptor != null ? descriptor : ""); - Identifier frequency = event.getIdentifierCollection() - .getIdentifier(IdentifierClass.CONFIGURATION, Form.CHANNEL_FREQUENCY, Role.ANY); - - if(frequency instanceof FrequencyConfigurationIdentifier) + if(descriptor != null) { - cells.add(mFrequencyFormat - .format(((FrequencyConfigurationIdentifier)frequency).getValue() / 1e6d)); - + cells.add(mFrequencyFormat.format(descriptor.getDownlinkFrequency() / 1e6d)); } else { - cells.add(""); + Identifier frequency = event.getIdentifierCollection() + .getIdentifier(IdentifierClass.CONFIGURATION, Form.CHANNEL_FREQUENCY, Role.ANY); + + if(frequency instanceof FrequencyConfigurationIdentifier) + { + cells.add(mFrequencyFormat + .format(((FrequencyConfigurationIdentifier)frequency).getValue() / 1e6d)); + + } + else + { + cells.add(""); + } } if(event.hasTimeslot()) @@ -163,6 +170,8 @@ private String toCSV(IDecodeEvent event) String details = event.getDetails(); cells.add(details != null ? details : ""); + cells.add(event.hashCode()); + return mCsvFormat.format(cells.toArray()); } } diff --git a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java index 951304361..dae250e44 100644 --- a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java +++ b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -21,31 +21,41 @@ import io.github.dsheirer.identifier.patch.PatchGroup; import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.preference.identifier.IntegerFormat; public class APCO25TalkgroupFormatter extends AbstractIntegerFormatter { - public static final int GROUP_DECIMAL_WIDTH = 5; - public static final int UNIT_DECIMAL_WIDTH = 8; - public static final int GROUP_HEXADECIMAL_WIDTH = 4; - public static final int UNIT_HEXADECIMAL_WIDTH = 6; + public static final int RADIO_DECIMAL_WIDTH = 8; + public static final int RADIO_HEXADECIMAL_WIDTH = 6; + public static final int SYSTEM_HEXADECIMAL_WIDTH = 3; + public static final int TALKGROUP_DECIMAL_WIDTH = 5; + public static final int TALKGROUP_HEXADECIMAL_WIDTH = 4; + public static final int WACN_HEXADECIMAL_WIDTH = 5; + /** * Formats the individual or group identifier to the specified format and width. */ public static String format(TalkgroupIdentifier identifier, IntegerFormat format, boolean fixedWidth) { + if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti) + { + return format(fqti, format, fixedWidth); + } + if(fixedWidth) { switch(format) { case DECIMAL: case FORMATTED: - return toDecimal(identifier.getValue(), GROUP_DECIMAL_WIDTH); + return toDecimal(identifier.getValue(), TALKGROUP_DECIMAL_WIDTH); case HEXADECIMAL: - return toHex(identifier.getValue(), GROUP_HEXADECIMAL_WIDTH); + return toHex(identifier.getValue(), TALKGROUP_HEXADECIMAL_WIDTH); default: throw new IllegalArgumentException("Unrecognized integer format: " + format); } @@ -65,6 +75,62 @@ public static String format(TalkgroupIdentifier identifier, IntegerFormat format } } + /** + * Formats the fully qualified group identifier to the specified format and width. + */ + public static String format(FullyQualifiedTalkgroupIdentifier identifier, IntegerFormat format, boolean fixedWidth) + { + int id = identifier.getValue(); + int wacn = identifier.getWacn(); + int system = identifier.getSystem(); + int talkgroup = identifier.getTalkgroup(); + + StringBuilder sb = new StringBuilder(); + + if(fixedWidth) + { + switch(format) + { + case DECIMAL: + case FORMATTED: + sb.append(toDecimal(id, TALKGROUP_DECIMAL_WIDTH)); + sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); + sb.append(".").append(toDecimal(talkgroup, TALKGROUP_DECIMAL_WIDTH)).append(")"); + return sb.toString(); + case HEXADECIMAL: + sb.append(toHex(id, TALKGROUP_HEXADECIMAL_WIDTH)); + sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(talkgroup, TALKGROUP_HEXADECIMAL_WIDTH)).append(")"); + return sb.toString(); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + else + { + switch(format) + { + case DECIMAL: + case FORMATTED: + sb.append(id); + sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); + sb.append(".").append(talkgroup).append(")"); + return sb.toString(); + case HEXADECIMAL: + sb.append(toHex(id)); + sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(talkgroup)).append(")"); + return sb.toString(); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + } + /** * Formats the patch group to the specified format and width */ @@ -110,15 +176,20 @@ public static String format(PatchGroupIdentifier identifier, IntegerFormat forma */ public static String format(RadioIdentifier identifier, IntegerFormat format, boolean fixedWidth) { + if(identifier instanceof FullyQualifiedRadioIdentifier fqri) + { + return format(fqri, format, fixedWidth); + } + if(fixedWidth) { switch(format) { case DECIMAL: case FORMATTED: - return toDecimal(identifier.getValue(), UNIT_DECIMAL_WIDTH); + return toDecimal(identifier.getValue(), RADIO_DECIMAL_WIDTH); case HEXADECIMAL: - return toHex(identifier.getValue(), UNIT_HEXADECIMAL_WIDTH); + return toHex(identifier.getValue(), RADIO_HEXADECIMAL_WIDTH); default: throw new IllegalArgumentException("Unrecognized integer format: " + format); } @@ -138,6 +209,62 @@ public static String format(RadioIdentifier identifier, IntegerFormat format, bo } } + /** + * Formats the fully qualified radio identifier to the specified format and width. + */ + public static String format(FullyQualifiedRadioIdentifier identifier, IntegerFormat format, boolean fixedWidth) + { + int id = identifier.getValue(); + int wacn = identifier.getWacn(); + int system = identifier.getSystem(); + int radio = identifier.getRadio(); + + StringBuilder sb = new StringBuilder(); + + if(fixedWidth) + { + switch(format) + { + case DECIMAL: + case FORMATTED: + sb.append(toDecimal(id, RADIO_DECIMAL_WIDTH)); + sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); + sb.append(".").append(toDecimal(radio, RADIO_DECIMAL_WIDTH)).append(")"); + return sb.toString(); + case HEXADECIMAL: + sb.append(toHex(id, RADIO_HEXADECIMAL_WIDTH)); + sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(radio, RADIO_HEXADECIMAL_WIDTH)).append(")"); + return sb.toString(); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + else + { + switch(format) + { + case DECIMAL: + case FORMATTED: + sb.append(id); + sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); + sb.append(".").append(radio).append(")"); + return sb.toString(); + case HEXADECIMAL: + sb.append(toHex(id)); + sb.append("(").append(toHex(wacn, WACN_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(system, SYSTEM_HEXADECIMAL_WIDTH)); + sb.append(".").append(toHex(radio)).append(")"); + return sb.toString(); + default: + throw new IllegalArgumentException("Unrecognized integer format: " + format); + } + } + } + @Override public String format(int value, IntegerFormat integerFormat) { diff --git a/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java b/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java index 05e140181..611e13d01 100644 --- a/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java +++ b/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * 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 @@ -35,17 +35,16 @@ import io.github.dsheirer.util.StringUtils; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Monitors audio segments and upon completion records any audio segments that have been flagged as recordable @@ -112,14 +111,14 @@ public void processCompletedAudioSegment(AudioSegment audioSegment) //Debug if(audioSegment.getAudioBufferCount() == 0) { - mLog.debug("Audio Segment detected with 0 audio buffers"); +// mLog.debug("Audio Segment detected with 0 audio buffers"); } List toIdentifiers = audioSegment.getIdentifierCollection().getIdentifiers(Role.TO); if(toIdentifiers.isEmpty()) { - mLog.debug("Audio Segment detected with NO TO identifiers"); +// mLog.debug("Audio Segment detected with NO TO identifiers"); } if(audioSegment.recordAudioProperty().get()) diff --git a/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java b/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java index 6a2630eb3..d44dfbec8 100644 --- a/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java +++ b/src/main/java/io/github/dsheirer/record/binary/BinaryReader.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -36,6 +36,7 @@ public class BinaryReader implements Iterator, AutoCloseable private InputStream mInputStream; private Path mPath; private ByteBuffer mNextBuffer; + private long mByteCounter; /** * Constructs a binary reader @@ -83,6 +84,14 @@ public ByteBuffer next() return current; } + /** + * Total number of bytes read from file + */ + public long getByteCounter() + { + return mByteCounter; + } + /** * Loads the next buffer */ @@ -92,6 +101,7 @@ private void getNext() { byte[] readBytes = new byte[mBufferSize]; int bytesRead = mInputStream.read(readBytes); + mByteCounter += bytesRead; if(bytesRead < readBytes.length && bytesRead > 0) { diff --git a/src/main/java/io/github/dsheirer/service/radioreference/RadioReference.java b/src/main/java/io/github/dsheirer/service/radioreference/RadioReference.java index e259c2ca5..3e2e86267 100644 --- a/src/main/java/io/github/dsheirer/service/radioreference/RadioReference.java +++ b/src/main/java/io/github/dsheirer/service/radioreference/RadioReference.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2020 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 - * * ***************************************************************************** + * 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.service.radioreference; @@ -29,16 +26,18 @@ import io.github.dsheirer.rrapi.response.Fault; import io.github.dsheirer.rrapi.type.AuthorizationInformation; import io.github.dsheirer.rrapi.type.UserInfo; -import javafx.beans.property.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.Scanner; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Service interface to radioreference.com data API with caching for Flavor, Mode, Type and Tag values. @@ -66,7 +65,7 @@ public enum LoginStatus UNKNOWN, INVALID_LOGIN, EXPIRED_PREMIUM, - VALID_PREMIUM + VALID_PREMIUM; } /** @@ -188,6 +187,11 @@ public static LoginStatus testConnectionWithExp(String userName, String password RadioReferenceService service = new RadioReferenceService(credentials); UserInfo ui = service.getUserInfo(); + if(ui == null) + { + throw new RadioReferenceException("The Radio Reference service is not providing user account details"); + } + return CheckExpDate(ui.getExpirationDate()); } catch (RadioReferenceException rre) @@ -266,9 +270,18 @@ private void login() try { UserInfo userInfo = getService().getUserInfo(); - accountExpiresProperty().setValue(userInfo.getExpirationDate()); - mLoginStatus = CheckExpDate(userInfo.getExpirationDate()); + if(userInfo != null) + { + accountExpiresProperty().setValue(userInfo.getExpirationDate()); + mLoginStatus = CheckExpDate(userInfo.getExpirationDate()); + } + else + { + accountExpiresProperty().setValue(null); + mLoginStatus = LoginStatus.UNKNOWN; + } + if(mLoginStatus == LoginStatus.VALID_PREMIUM) { availableProperty().set(true); diff --git a/src/main/java/io/github/dsheirer/source/tuner/channel/rotation/ChannelRotationMonitor.java b/src/main/java/io/github/dsheirer/source/tuner/channel/rotation/ChannelRotationMonitor.java index b2eae315c..1d43629c6 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/channel/rotation/ChannelRotationMonitor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/channel/rotation/ChannelRotationMonitor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * 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 @@ -30,12 +30,11 @@ import io.github.dsheirer.source.ISourceEventProvider; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.util.ThreadPool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.Collection; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Monitors channel state to detect when a channel is not in an identified active state and issues a request to rotate @@ -63,10 +62,11 @@ public class ChannelRotationMonitor extends Module implements ISourceEventProvid * @param activeStates to monitor * @param rotationDelay specifies how long to remain on each frequency before rotating (in milliseconds). */ - public ChannelRotationMonitor(Collection activeStates, long rotationDelay) + public ChannelRotationMonitor(Collection activeStates, long rotationDelay, UserPreferences userPreferences) { mActiveStates = activeStates; mRotationDelay = rotationDelay; + mUserPreferences = userPreferences; if(mRotationDelay == 0) { diff --git a/src/main/java/io/github/dsheirer/source/tuner/ui/TunerEditor.java b/src/main/java/io/github/dsheirer/source/tuner/ui/TunerEditor.java index bb63c2af0..775dc749f 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/ui/TunerEditor.java +++ b/src/main/java/io/github/dsheirer/source/tuner/ui/TunerEditor.java @@ -540,8 +540,13 @@ protected JButton getResetFrequenciesButton() { mResetFrequenciesButton = new JButton("Reset"); mResetFrequenciesButton.addActionListener(e -> { - getMinimumFrequencyTextField().setFrequency(getMinimumTunableFrequency()); - getMaximumFrequencyTextField().setFrequency(getMaximumTunableFrequency()); + + long min = getMinimumTunableFrequency(); + long max = getMaximumTunableFrequency(); + getTuner().getTunerController().setFrequencyExtents(min, max); + getMinimumFrequencyTextField().setFrequency(min); + getMaximumFrequencyTextField().setFrequency(max); + save(); }); }