From 1b3ce431e664e7d8ceb275e8f27625d50cbf9482 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Sun, 13 Oct 2024 11:53:12 -0400 Subject: [PATCH] #2007 P25 Call events tracking. P25 Traffic channel manager now uses event trackers to deconflict between control channel and traffic channel event updates. Protects against Motorola ISSI FQSUID that's all zeros. Resolves issue with running out of traffic channels after losing the control channel and going into CC hunt mode a few times. (#2010) Co-authored-by: Dennis Sheirer --- .../dsheirer/gui/viewer/P25P2Viewer.java | 2 +- .../identifier/IdentifierCollection.java | 9 + .../module/decode/DecoderFactory.java | 34 +- .../module/decode/event/DecodeEventType.java | 2 +- .../decode/p25/P25ChannelGrantEvent.java | 14 +- .../p25/P25TrafficChannelEventTracker.java | 284 +++++ .../decode/p25/P25TrafficChannelManager.java | 1128 ++++++++--------- .../p25/identifier/channel/APCO25Channel.java | 59 +- .../p25/identifier/channel/P25Channel.java | 16 + .../decode/p25/phase1/P25P1DecoderState.java | 253 ++-- .../lc/standard/LCGroupVoiceChannelUser.java | 2 +- .../lc/standard/LCSourceIDExtension.java | 10 + .../decode/p25/phase2/P25P2DecoderHDQPSK.java | 5 +- .../P25P2DecoderHDQPSKInstrumented.java | 4 +- .../decode/p25/phase2/P25P2DecoderState.java | 314 ++--- .../decode/p25/phase2/P25P2MessageFramer.java | 76 +- .../decode/p25/reference/ServiceOptions.java | 2 +- 17 files changed, 1132 insertions(+), 1082 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelEventTracker.java diff --git a/src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java b/src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java index b5a1d340..4bf91761 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java @@ -222,7 +222,7 @@ public class P25P2Viewer extends VBox ThreadPool.CACHED.submit(() -> { List messages = new ArrayList<>(); - P25P2MessageFramer messageFramer = new P25P2MessageFramer(null, 9600); + P25P2MessageFramer messageFramer = new P25P2MessageFramer(null); messageFramer.setScrambleParameters(scrambleParameters); P25P2MessageProcessor messageProcessor = new P25P2MessageProcessor(); messageFramer.setListener(messageProcessor); diff --git a/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java b/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java index 12eaf108..7d710ed8 100644 --- a/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java +++ b/src/main/java/io/github/dsheirer/identifier/IdentifierCollection.java @@ -363,6 +363,15 @@ public class IdentifierCollection return to; } + /** + * Returns an encryption key identiier or null. + * @return key or null. + */ + public Identifier getEncryptionIdentifier() + { + return getIdentifier(IdentifierClass.USER, Form.ENCRYPTION_KEY, Role.ANY); + } + @Override public String toString() { 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 861094c5..89a113b6 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java @@ -188,10 +188,10 @@ public class DecoderFactory processPassport(channel, modules, aliasList, decodeConfig); break; case P25_PHASE1: - processP25Phase1(channel, userPreferences, modules, aliasList, trafficChannelManager); + processP25Phase1(channel, userPreferences, modules, aliasList, trafficChannelManager, channelDescriptor); break; case P25_PHASE2: - processP25Phase2(channel, userPreferences, modules, aliasList, trafficChannelManager); + processP25Phase2(channel, userPreferences, modules, aliasList, trafficChannelManager, channelDescriptor); break; default: throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]"); @@ -210,7 +210,8 @@ public class DecoderFactory * will be created automatically. */ private static void processP25Phase2(Channel channel, UserPreferences userPreferences, List modules, - AliasList aliasList, TrafficChannelManager trafficChannelManager) + AliasList aliasList, TrafficChannelManager trafficChannelManager, + IChannelDescriptor channelDescriptor) { modules.add(new P25P2DecoderHDQPSK((DecodeConfigP25Phase2)channel.getDecodeConfiguration())); @@ -236,22 +237,25 @@ public class DecoderFactory modules.add(p25TrafficChannelManager); } + //A single patch group manager is shared across both timeslots PatchGroupManager patchGroupManager = new PatchGroupManager(); - modules.add(new P25P2DecoderState(channel, P25P2Message.TIMESLOT_1, p25TrafficChannelManager, patchGroupManager)); - modules.add(new P25P2DecoderState(channel, P25P2Message.TIMESLOT_2, p25TrafficChannelManager, patchGroupManager)); + + P25P2DecoderState decoderState1 = new P25P2DecoderState(channel, P25P2Message.TIMESLOT_1, p25TrafficChannelManager, patchGroupManager); + decoderState1.setCurrentChannel(channelDescriptor); + P25P2DecoderState decoderState2 = new P25P2DecoderState(channel, P25P2Message.TIMESLOT_2, p25TrafficChannelManager, patchGroupManager); + decoderState2.setCurrentChannel(channelDescriptor); + modules.add(decoderState1); + modules.add(decoderState2); 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()) + 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(), - userPreferences)); + modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences)); } } @@ -263,7 +267,8 @@ public class DecoderFactory * @param aliasList for the channel */ private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List modules, - AliasList aliasList, TrafficChannelManager trafficChannelManager) + AliasList aliasList, TrafficChannelManager trafficChannelManager, + IChannelDescriptor channelDescriptor) { if(channel.getDecodeConfiguration() instanceof DecodeConfigP25Phase1 p1) { @@ -288,7 +293,9 @@ public class DecoderFactory } else if(trafficChannelManager instanceof P25TrafficChannelManager parentTCM) { - modules.add(new P25P1DecoderState(channel, parentTCM)); + P25P1DecoderState decoderState = new P25P1DecoderState(channel, parentTCM); + decoderState.setCurrentChannel(channelDescriptor); + modules.add(decoderState); } else { @@ -466,6 +473,7 @@ public class DecoderFactory * @param aliasList for the audio module * @param decodeConfig for the DMR configuration * @param trafficChannelManager optional traffic channel manager to re-use + * @param channelDescriptor for the channel. */ private static void processDMR(Channel channel, UserPreferences userPreferences, List modules, AliasList aliasList, DecodeConfigDMR decodeConfig, 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 8686f6b6..9672b750 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 @@ -95,7 +95,7 @@ public enum DecodeEventType /** * Voice call event types for filtering */ - public static final EnumSet VOICE_CALLS = EnumSet.of(DecodeEventType.CALL_GROUP, + public static final EnumSet VOICE_CALLS = EnumSet.of(DecodeEventType.CALL, DecodeEventType.CALL_GROUP, DecodeEventType.CALL_PATCH_GROUP, DecodeEventType.CALL_ALERT, DecodeEventType.CALL_DETECT, DecodeEventType.CALL_DO_NOT_MONITOR, DecodeEventType.CALL_END, DecodeEventType.CALL_INTERCONNECT, DecodeEventType.CALL_UNIQUE_ID, DecodeEventType.CALL_UNIT_TO_UNIT, DecodeEventType.CALL_NO_TUNER, 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 0e4e694a..688256a7 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 @@ -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 @@ -80,6 +80,7 @@ public class P25ChannelGrantEvent extends P25DecodeEvent protected IChannelDescriptor mChannelDescriptor; protected String mDetails; private ServiceOptions mServiceOptions; + private int mTimeslot = -1; /** * Constructs a builder instance with the specified start time in milliseconds @@ -144,6 +145,16 @@ public class P25ChannelGrantEvent extends P25DecodeEvent return this; } + /** + * Sets the timeslot for the event + * @param timeslot + */ + public P25ChannelGrantDecodeEventBuilder timeslot(int timeslot) + { + mTimeslot = timeslot; + return this; + } + /** * Builds the decode event */ @@ -156,6 +167,7 @@ public class P25ChannelGrantEvent extends P25DecodeEvent decodeEvent.setDuration(mDuration); decodeEvent.setIdentifierCollection(mIdentifierCollection); decodeEvent.setServiceOptions(mServiceOptions); + decodeEvent.setTimeslot(mTimeslot); return decodeEvent; } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelEventTracker.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelEventTracker.java new file mode 100644 index 00000000..beedd105 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelEventTracker.java @@ -0,0 +1,284 @@ +/* + * ***************************************************************************** + * 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; + +import io.github.dsheirer.channel.IChannelDescriptor; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wrapper to track the state of a traffic channel event to manage updates from the control channel and the traffic + * channel and to assist in determining when the communicants of a traffic channel have changed, indicating the need + * for a new event. + */ +public class P25TrafficChannelEventTracker +{ + private static final Logger LOGGER = LoggerFactory.getLogger(P25TrafficChannelEventTracker.class); + private static final long STALE_EVENT_THRESHOLD_MS = 2000; + private P25ChannelGrantEvent mEvent; + private boolean mStarted = false; + private boolean mComplete = false; + + /** + * Constructs an instance + * @param event to track for the traffic channel. + */ + public P25TrafficChannelEventTracker(P25ChannelGrantEvent event) + { + mEvent = event; + } + + /** + * Access the underlying traffic channel event that is being tracked. + * @return event. + */ + public P25ChannelGrantEvent getEvent() + { + return mEvent; + } + + /** + * Indicates if this event is stale relative to the provided timestamp. Staleness is determined by the time delta + * between the event's end time and timestamp argument. + * @param timestamp to check for staleness + * @return true if the delta time exceeds a threshold. + */ + public boolean isStale(long timestamp) + { + if(getEvent().getTimeEnd() > 0) + { + return timestamp - getEvent().getTimeEnd() > STALE_EVENT_THRESHOLD_MS; + } + + return timestamp - getEvent().getTimeStart() > STALE_EVENT_THRESHOLD_MS; + } + + /** + * Adds the identifier to the tracked event if the event's identifier collection does not already have it. + * @param identifier to add + */ + public void addIdentifierIfMissing(Identifier identifier) + { + if(identifier != null && !getEvent().getIdentifierCollection().hasIdentifier(identifier)) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(getEvent().getIdentifierCollection() + .getIdentifiers()); + mic.update(identifier); + getEvent().setIdentifierCollection(mic); + } + } + + /** + * Adds the additional details to the tracked event if the current event details are null or if they do not contain + * the additional details. + * @param additionalDetails + */ + public void addDetailsIfMissing(String additionalDetails) + { + if(additionalDetails != null && !additionalDetails.isEmpty()) + { + if(getEvent().getDetails() == null) + { + getEvent().setDetails(additionalDetails); + } + else if(!getEvent().getDetails().endsWith(additionalDetails)) + { + getEvent().setDetails(getEvent().getDetails() + " " + additionalDetails); + } + } + } + + /** + * Compares the TO role identifier(s) from the tracked event and the identifier collection argument for equality + * and also checks this event for staleness. + * + * @param toCompare containing a TO identifier + * @param timestamp to check for staleness + * @return true if both collections contain a TO identifier and the TO identifiers are the same value + */ + public boolean isSameCallCheckingToOnly(IdentifierCollection toCompare, long timestamp) + { + Identifier currentTO = getEvent().getIdentifierCollection().getToIdentifier(); + Identifier nextTO = toCompare.getToIdentifier(); + return currentTO != null && currentTO.equals(nextTO) && !isStale(timestamp); + } + + /** + * Indicates if the tracked event from identifier is non null and that it is different to the from argument. + * @param fromToCompare against the current event from identifier. + * @return true if they are different. + */ + public boolean isDifferentTalker(Identifier fromToCompare) + { + if(fromToCompare == null) + { + return false; + } + + Identifier fromCurrent = getEvent().getIdentifierCollection().getFromIdentifier(); + return fromCurrent != null && !fromCurrent.equals(fromToCompare); + } + + + /** + * Checks the call for staleness and verifies that the call event TO identifier is equal to the TO identifier + * in the provided identifier collection. + * @param toCompareIC containing a TO and optionally a FROM identifier to compare. + * @param timestamp to check for staleness + * @return true if the call identifiers are the same and the call is not stale. + */ + public boolean isSameCallCheckingToAndFrom(IdentifierCollection toCompareIC, long timestamp) + { + if(!isStale(timestamp)) + { + Identifier currentTO = getEvent().getIdentifierCollection().getToIdentifier(); + Identifier nextTO = toCompareIC.getToIdentifier(); + + if(currentTO != null && currentTO.equals(nextTO)) + { + Identifier existingFROM = getEvent().getIdentifierCollection().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(existingFROM == null || existingFROM.getForm() == Form.TALKER_ALIAS) + { + return true; + } + + Identifier nextFROM = toCompareIC.getFromIdentifier(); + + //Sometime the GROUP_VOICE_CHANNEL_USER has a zero valued FROM address which is not valid, so if the + //nextFROM is null, we consider the call to be the same. Likewise, if the nextFROM is a talker alias, + //that's also the same call. + if(nextFROM == null || (nextFROM != null && nextFROM.getForm() == Form.TALKER_ALIAS)) + { + return true; + } + + return existingFROM.equals(nextFROM); + } + } + + return false; + } + + /** + * Indicates if the event has been marked as complete by traffic channel signalling. + * @return complete status. + */ + public boolean isComplete() + { + return mComplete; + } + + /** + * Indicates if the event has been marked as started by the traffic channel for HDU or LDU signalling. + * @return started status. + */ + public boolean isStarted() + { + return mStarted; + } + + /** + * Updates the event duration using signalling from the control channel. + * + * Note: once the traffic channel starts updating the event timing, attempts to update timing from the control + * channel are ignored. + * + * @param timestamp to use as the current end timestamp for the event. + * @return true if the timestamp was updated. + */ + public boolean updateDurationControl(long timestamp) + { + if(!isStarted()) + { + getEvent().update(timestamp); + return true; + } + + return false; + } + + /** + * Updates the event duration using signalling from the traffic channel. + * + * Note: once the event is being updated from the traffic channel, any attempts to update from the control channel + * are ignored. + * @param timestamp to assign. + */ + public void updateDurationTraffic(long timestamp) + { + if(!isComplete()) + { + mStarted = true; + getEvent().update(timestamp); + } + else + { + LOGGER.warn("Attempt to update event call event duration from traffic channel against an event that is marked as complete"); + } + } + + /** + * Mark the event as complete and assign final end timestamp to the event. + * + * Note: further attempts to complete an already complete event are ignored. + * @param timestamp to assign. + * @return true if the timestamp was updated + */ + public boolean completeTraffic(long timestamp) + { + if(!isComplete()) + { + mComplete = true; + getEvent().end(timestamp); + return true; + } + + return false; + } + + /** + * Updates the details for the tracked event. + * @param details to update + */ + public void setDetails(String details) + { + getEvent().setDetails(details); + } + + /** + * Updates the channel descriptor for the tracked event. + * @param channelDescriptor to update. + */ + public void addChannelDescriptorIfMissing(IChannelDescriptor channelDescriptor) + { + if(channelDescriptor != null && getEvent().getChannelDescriptor() == null) + { + getEvent().setChannelDescriptor(channelDescriptor); + } + } +} 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 24d3f003..905133b0 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 @@ -26,7 +26,6 @@ import io.github.dsheirer.controller.channel.ChannelEvent.Event; 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; @@ -48,7 +47,6 @@ import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitCha 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; @@ -75,21 +73,27 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Monitors channel grant and channel grant update messages to allocate traffic channels to capture - * traffic channel activity. + * Monitors traffic channel state for a control channel and monitors individual traffic channel updates to track all + * call events across the system. * - * Creates and reuses a limited set of Channel instances each with a TRAFFIC channel type. Since each of - * the traffic channels will be using the same decoder type and configuration options, we reuse each of the - * channel instances to allow the ChannelProcessingManager to reuse a cached set of processing chains that - * are created as each channel is activated. + * Call events originate on the control channel and receive continuing updates from the control channel. Events + * continue on the traffic channel. The call event trackers manage the call event until call end where the call + * duration is updated from the control messaging and once the traffic channel is allocated the tracker only accepts + * event timing updates from the traffic messaging and at the end of the call the tracker marks the event as complete. + * Further updates to an ended event will cause the event to be removed and replaced with the next tracked event. * - * Each traffic channel is activated by sending a ChannelEvent via the channel model. The channel processing - * manager receives the activation request. If successful, a processing chain is activated for the traffic - * channel. Otherwise, a channel event is broadcast indicating that the channel could not be activated. On - * teardown of an activated traffic channel, a channel event is broadcast to indicate the traffic channels - * is no longer active. + * Creates and reuses a limited set of Channel instances each with a TRAFFIC channel type. Since each of the traffic + * channels will be using the same decoder type and configuration options, we reuse each of the channel instances to + * allow the ChannelProcessingManager to reuse a cached set of processing chains that are created as each channel is + * activated. * - * This manager monitors channel events watching for events related to managed traffic channels. + * Each traffic channel is activated by sending a ChannelEvent via the channel model. The channel processing manager + * receives the activation request. If successful, a processing chain is activated for the traffic channel. Otherwise, + * a channel event is broadcast indicating that the channel could not be activated. On teardown of an activated traffic + * channel, a channel event is broadcast to indicate the traffic channels is no longer active. + * + * The ReentrantLock (mLock) protects threaded access to the mTSxChannelGrantEventMaps and to the allocated and + * available traffic channel lists and queues. */ public class P25TrafficChannelManager extends TrafficChannelManager implements IDecodeEventProvider, IChannelEventListener, IChannelEventProvider, IMessageListener @@ -98,15 +102,15 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I 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 static final long STALE_EVENT_THRESHOLD_MS = 2000; private Queue mAvailablePhase1TrafficChannelQueue = new LinkedTransferQueue<>(); - private List mManagedPhase1TrafficChannels; private Queue mAvailablePhase2TrafficChannelQueue = new LinkedTransferQueue<>(); + private List mManagedPhase1TrafficChannels; private List mManagedPhase2TrafficChannels; private Map mAllocatedTrafficChannelMap = new HashMap<>(); - private Map mTS1ChannelGrantEventMap = new HashMap<>(); - private Map mTS2ChannelGrantEventMap = new HashMap<>(); + private Map mTS1ChannelGrantEventMap = new HashMap<>(); + private Map mTS2ChannelGrantEventMap = new HashMap<>(); + private ReentrantLock mLock = new ReentrantLock(); private Map mFrequencyBandMap = new ConcurrentHashMap<>(); private Listener mChannelEventListener; private Listener mDecodeEventListener; @@ -115,10 +119,8 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I 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(); private TalkerAliasManager mTalkerAliasManager = new TalkerAliasManager(); /** @@ -182,19 +184,21 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I { //Shutdown all existing traffic channels and clear the maps. List trafficChannelsToDisable = new ArrayList<>(mAllocatedTrafficChannelMap.values()); - mAllocatedTrafficChannelMap.clear(); - for(Channel channel : trafficChannelsToDisable) + for(Channel channelToDisable : trafficChannelsToDisable) { - if(!parentChannel.equals(channel)) + if(!parentChannel.equals(channelToDisable)) { - broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE)); + broadcast(new ChannelEvent(channelToDisable, Event.REQUEST_DISABLE)); } } mTS1ChannelGrantEventMap.clear(); mTS2ChannelGrantEventMap.clear(); + //Remove the control channel from the previous frequency + mAllocatedTrafficChannelMap.remove(previous); + //Store the current control channel in the allocated channel map so that we don't allocate a traffic channel against it mAllocatedTrafficChannelMap.put(current, parentChannel); } @@ -283,6 +287,100 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I } } + /** + * Broadcasts the decode event from the tracker. + * @param tracker containing a decode event. + */ + public void broadcast(P25TrafficChannelEventTracker tracker) + { + broadcast(tracker.getEvent()); + } + + /** + * Retrieves the current event tracker for the specified channel and if the tracker is stale relative to the + * timestamp, returns null, otherwise returns the current tracker. + * @param channel to look up the tracker + * @param timestamp to compare for staleness + * @return tracker or null + */ + private P25TrafficChannelEventTracker getTrackerRemoveIfStale(APCO25Channel channel, long timestamp) + { + return getTrackerRemoveIfStale(channel.getDownlinkFrequency(), channel.getTimeslot(), timestamp); + } + + /** + * Retrieves the current event tracker for the specified channel and if the tracker is stale relative to the + * timestamp, returns null, otherwise returns the current tracker. + * @param frequency to look up the tracker + * @param timeslot to look up the tracker + * @param timestamp to compare for staleness + * @return tracker or null + */ + private P25TrafficChannelEventTracker getTrackerRemoveIfStale(long frequency, int timeslot, long timestamp) + { + P25TrafficChannelEventTracker tracker = getTracker(frequency, timeslot); + + if(tracker != null && tracker.isStale(timestamp)) + { + removeTracker(frequency, timeslot); + tracker = null; + } + + return tracker; + } + + /** + * Gets the tracker from the correct channel grant map. + * @param frequency for the map lookup + * @param timeslot to identify the correct map. + */ + private P25TrafficChannelEventTracker getTracker(long frequency, int timeslot) + { + if(timeslot == P25P1Message.TIMESLOT_2) + { + return mTS2ChannelGrantEventMap.get(frequency); + } + else + { + return mTS1ChannelGrantEventMap.get(frequency); + } + } + + /** + * Adds the tracker to the correct channel grant map. + * @param tracker to add + * @param frequency for the map lookup + * @param timeslot to identify the correct map. + */ + private void addTracker(P25TrafficChannelEventTracker tracker, long frequency, int timeslot) + { + if(timeslot == P25P1Message.TIMESLOT_2) + { + mTS2ChannelGrantEventMap.put(frequency, tracker); + } + else + { + mTS1ChannelGrantEventMap.put(frequency, tracker); + } + } + + /** + * Removes the tracker from the correct channel grant map. + * @param frequency for the map lookup + * @param timeslot to identify the correct map. + */ + private void removeTracker(long frequency, int timeslot) + { + if(timeslot == P25P1Message.TIMESLOT_2) + { + mTS2ChannelGrantEventMap.remove(frequency); + } + else + { + mTS1ChannelGrantEventMap.remove(frequency); + } + } + /** * 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. @@ -297,7 +395,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I * @param timestamp of the message */ public void processP2ChannelUpdate(APCO25Channel channel, ServiceOptions serviceOptions, - IdentifierCollection ic, MacOpcode macOpcode, long timestamp) + IdentifierCollection ic, MacOpcode macOpcode, long timestamp, String context) { if(channel.getDownlinkFrequency() > 0) { @@ -309,7 +407,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I if(!processing) { - processP2ChannelGrant(channel, serviceOptions, ic, macOpcode, timestamp); + processP2ChannelGrant(channel, serviceOptions, ic, macOpcode, timestamp, context); } } finally @@ -323,95 +421,102 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I * 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 + * @param timestamp for the final duration update for the event + * @return true if the current tracked call event was ended */ - public void closeP2CallEvent(long frequency, int timeslot, boolean isIdleNull) + public boolean processP2TrafficCallEnd(long frequency, int timeslot, long timestamp, String context) { - /** - * 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; - } + boolean completed = false; mLock.lock(); try { - if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) + P25TrafficChannelEventTracker tracker = getTracker(frequency, timeslot); + + //If we have a tracker that we can mark complete, broadcast the updated tracker/event. + if(tracker != null && tracker.completeTraffic(timestamp)) { - mTS1ChannelGrantEventMap.remove(frequency); - } - else - { - mTS2ChannelGrantEventMap.remove(frequency); + completed = true; + broadcast(tracker); } } finally { mLock.unlock(); } + + return completed; } /** - * Updates an identifier for an ongoing call event on the frequency and timeslot. + * Processes a traffic channel end push to talk event. The PTT event is handled separately from the call end + * method because there can be a timing issue between when the control channel issues the channel grant event and + * when the traffic channel closes out traffic on the same channel, which can cause an event to be closed + * prematurely. This method checks to see if the call has been started before closing it out. + * @param frequency for the channel + * @param timeslot for the channel. + * @param timestamp for the final duration update for the event + * @return true if the current tracked call event was ended + */ + public boolean processP2TrafficEndPushToTalk(long frequency, int timeslot, long timestamp, String context) + { + boolean completed = false; + + mLock.lock(); + + try + { + P25TrafficChannelEventTracker tracker = getTracker(frequency, timeslot); + + //If we have a tracker that is started that we can mark complete, broadcast the updated tracker/event. + if(tracker != null && tracker.isStarted() && tracker.completeTraffic(timestamp)) + { + completed = true; + broadcast(tracker); + } + } + finally + { + mLock.unlock(); + } + + return completed; + } + + /** + * Updates an identifier and call duration 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. + * This is used primarily to add encryption, GPS, talker alias, etc. but can be used for any identifier update + * where the update infers that a call event is ongoing. * * @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) + public void processP2TrafficCurrentUser(long frequency, int timeslot, Identifier identifier, long timestamp) { mLock.lock(); try { - DecodeEvent event = null; + P25TrafficChannelEventTracker tracker = getTrackerRemoveIfStale(frequency, timeslot, timestamp); - if(timeslot == P25P1Message.TIMESLOT_1) + if(tracker != null && tracker.isComplete()) { - event = mTS1ChannelGrantEventMap.get(frequency); - - if(isStaleEvent(event, timestamp)) - { - event = null; - mTS1ChannelGrantEventMap.remove(frequency); - } - } - else if(timeslot == P25P1Message.TIMESLOT_2) - { - event = mTS2ChannelGrantEventMap.get(frequency); - - if(isStaleEvent(event, timestamp)) - { - event = null; - mTS1ChannelGrantEventMap.remove(frequency); - } + removeTracker(frequency, timeslot); + tracker = null; } - if(event != null) + if(tracker != null) { - if(!event.getIdentifierCollection().hasIdentifier(identifier)) - { - MutableIdentifierCollection mic = new MutableIdentifierCollection(event.getIdentifierCollection() - .getIdentifiers()); - mic.update(identifier); - event.setIdentifierCollection(mic); - } - - event.update(timestamp); - broadcast(event); + tracker.addIdentifierIfMissing(identifier); + tracker.updateDurationTraffic(timestamp); + broadcast(tracker); } } finally @@ -420,10 +525,43 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I } } + /** + * Starts a tracked event and updates the duration for a tracked event. + * + * @param frequency for the call event + * @param timeslot for the call event + * @param timestamp for the update + */ + public void processP2TrafficVoice(long frequency, int timeslot, long timestamp) + { + mLock.lock(); + try + { + P25TrafficChannelEventTracker tracker = getTrackerRemoveIfStale(frequency, timeslot, timestamp); + + if(tracker != null && tracker.isComplete()) + { + removeTracker(frequency, timeslot); + tracker = null; + } + + if(tracker != null) + { + tracker.updateDurationTraffic(timestamp); + broadcast(tracker); + } + } + finally + { + mLock.unlock(); + } + } /** - * Processes a call on the current channel + * Processes mac messaging that indicates a call on the current channel. This update does not update the call + * duration for the event but will create an event if one does not exist. Updates to the call duration can only + * occur * @param frequency of the current channel * @param timeslot of the current channel * @param serviceOptions for the call @@ -431,86 +569,33 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I * @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) + public IChannelDescriptor processP2TrafficCurrentUser(long frequency, int timeslot, IChannelDescriptor channelDescriptor, + ServiceOptions serviceOptions, MacOpcode macOpcode, + IdentifierCollection ic, long timestamp, String additionalDetails, String context) { mLock.lock(); try { - DecodeEvent event = null; + P25TrafficChannelEventTracker tracker = getTrackerRemoveIfStale(frequency, timeslot, timestamp); - if(timeslot == P25P1Message.TIMESLOT_1) + if(tracker != null && tracker.isComplete()) { - event = mTS1ChannelGrantEventMap.get(frequency); - - if(isStaleEvent(event, timestamp)) - { - event = null; - mTS1ChannelGrantEventMap.remove(frequency); - } - } - else if(timeslot == P25P1Message.TIMESLOT_2) - { - event = mTS2ChannelGrantEventMap.get(frequency); - - if(isStaleEvent(event, timestamp)) - { - event = null; - mTS2ChannelGrantEventMap.remove(frequency); - } + removeTracker(frequency, timeslot); + tracker = null; } - if(event != null) + if(tracker != null && tracker.isSameCallCheckingToAndFrom(ic, timestamp)) { - if(isSameCallFull(event.getIdentifierCollection(), ic)) + for(Identifier identifier: ic.getIdentifiers()) { - 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(); + tracker.addIdentifierIfMissing(identifier); } + + tracker.addDetailsIfMissing(additionalDetails); + tracker.addChannelDescriptorIfMissing(channelDescriptor); + broadcast(tracker); + return tracker.getEvent().getChannelDescriptor(); } //Create a new event for the current call. @@ -522,16 +607,9 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I .identifiers(ic) .build(); - if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) - { - mTS1ChannelGrantEventMap.put(frequency, callEvent); - } - else - { - mTS2ChannelGrantEventMap.put(frequency, callEvent); - } - - broadcast(callEvent); + tracker = new P25TrafficChannelEventTracker(callEvent); + addTracker(tracker, frequency, timeslot); + broadcast(tracker); return null; } finally @@ -550,7 +628,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I * @param macOpcode to identify the call type for the event description */ public void processP2ChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, - IdentifierCollection ic, MacOpcode macOpcode, long timestamp) + IdentifierCollection ic, MacOpcode macOpcode, long timestamp, String context) { mLock.lock(); @@ -567,17 +645,13 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I if(macOpcode.isDataChannelGrant()) { APCO25Channel phase1Channel = convertPhase2ToPhase1(apco25Channel); - processPhase1ChannelGrant(phase1Channel, serviceOptions, ic, decodeEventType, - isDataChannelGrant, timestamp); + processPhase1ControlChannelGrant(phase1Channel, serviceOptions, ic, decodeEventType, + isDataChannelGrant, timestamp, context); } 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 @@ -588,8 +662,8 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I } else { - processPhase1ChannelGrant(apco25Channel, serviceOptions, ic, decodeEventType, isDataChannelGrant, - timestamp); + processPhase1ControlChannelGrant(apco25Channel, serviceOptions, ic, decodeEventType, isDataChannelGrant, + timestamp, context); } } @@ -599,17 +673,17 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I } } - /** - * 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) + /** + * Processes phase 1 channel grants directed by the control channel 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 granted traffic channel + * @param serviceOptions for the granted traffic channel - optional can be null + * @param ic associated with the channel grant + * @param opcode to identify the call type for the event description + */ + public void processP1ControlDirectedChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, + IdentifierCollection ic, Opcode opcode, long timestamp, String context) { mLock.lock(); @@ -620,13 +694,13 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I if(apco25Channel.isTDMAChannel()) { - processPhase2ChannelGrant(apco25Channel, serviceOptions, identifierCollection, decodeEventType, + processPhase2ChannelGrant(apco25Channel, serviceOptions, ic, decodeEventType, isDataChannelGrant, timestamp); } else { - processPhase1ChannelGrant(apco25Channel, serviceOptions, identifierCollection, decodeEventType, - isDataChannelGrant, timestamp); + processPhase1ControlChannelGrant(apco25Channel, serviceOptions, ic, decodeEventType, + isDataChannelGrant, timestamp, context); } } finally @@ -636,50 +710,43 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I } /** - * Updates an identifier for an ongoing call event on the frequency. + * Updates an identifier for an ongoing call event on the frequency and updates the event duration timestamp. * * 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. + * This is used primarily to add encryption, GPS, talker alias, etc. 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) + public void processP1TrafficCurrentUser(long frequency, Identifier identifier, long timestamp, String context) { mLock.lock(); try { - DecodeEvent event = mTS1ChannelGrantEventMap.get(frequency); + P25TrafficChannelEventTracker tracker = getTracker(frequency, P25P1Message.TIMESLOT_1); - if(event != null) + if(tracker != null && tracker.isComplete()) { - if(!event.getIdentifierCollection().hasIdentifier(identifier)) - { - MutableIdentifierCollection mic = new MutableIdentifierCollection(event.getIdentifierCollection() - .getIdentifiers()); - mic.update(identifier); - event.setIdentifierCollection(mic); - } + removeTracker(frequency, P25P1Message.TIMESLOT_1); + tracker = null; + } + + if(tracker != null) + { + tracker.addIdentifierIfMissing(identifier); //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); - } + tracker.addDetailsIfMissing(eki.toString()); } - event.update(timestamp); - broadcast(event); + tracker.updateDurationTraffic(timestamp); + broadcast(tracker); } } finally @@ -689,73 +756,42 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I } /** - * 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 + * Processes traffic channel announced current user information. + * @param frequency of the traffic channel + * @param channelDescriptor for the traffic channel + * @param decodeEventType to use if the call event is not currently tracked * @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) + public void processP1TrafficCurrentUser(long frequency, IChannelDescriptor channelDescriptor, + DecodeEventType decodeEventType, ServiceOptions serviceOptions, + IdentifierCollection ic, long timestamp, String additionalDetails, String context) { mLock.lock(); try { - DecodeEvent event = mTS1ChannelGrantEventMap.get(frequency); + P25TrafficChannelEventTracker tracker = getTracker(frequency, P25P1Message.TIMESLOT_1); - if(event != null) + if(tracker != null && tracker.isComplete()) { - 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(); - } + removeTracker(frequency, P25P1Message.TIMESLOT_1); + tracker = null; } - if(channelDescriptor == null && frequency > 0) + if(tracker != null && tracker.isSameCallCheckingToAndFrom(ic, timestamp)) { - channelDescriptor = new StandardChannel(frequency); + for(Identifier identifier: ic.getIdentifiers()) + { + tracker.addIdentifierIfMissing(identifier); + } + + tracker.updateDurationTraffic(timestamp); + tracker.addDetailsIfMissing(additionalDetails); + broadcast(tracker); + return; } //Create a new event for the current call. @@ -766,9 +802,9 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I .identifiers(ic) .build(); - mTS1ChannelGrantEventMap.put(frequency, callEvent); - broadcast(callEvent); - return null; + tracker = new P25TrafficChannelEventTracker(callEvent); + addTracker(tracker, frequency, P25P1Message.TIMESLOT_1); + broadcast(tracker); } finally { @@ -777,11 +813,12 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I } /** - * 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. + * Processes a P25 Phase 1 control channel announced update to an allocated traffic channel. If the initial traffic + * 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. + * From the update message we only get the TO talkgroup value, so we'll do a tracked event comparison using only + * the TO identifier to determine if the update refers to the currently tracked event or if the update is for a + * new and different event. * * @param channel where the activity is taking place. * @param serviceOptions for the call @@ -789,46 +826,31 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I * @param opcode for the update message * @param timestamp of the message */ - public void processP1ChannelUpdate(APCO25Channel channel, ServiceOptions serviceOptions, - IdentifierCollection ic, Opcode opcode, long timestamp) + public void processP1ControlAnnouncedTrafficUpdate(APCO25Channel channel, ServiceOptions serviceOptions, + IdentifierCollection ic, Opcode opcode, long timestamp, String context) { - mLock.lock(); try { - DecodeEvent event = null; + P25TrafficChannelEventTracker tracker = getTrackerRemoveIfStale(channel, timestamp); - if(channel.isTDMAChannel() && channel.getTimeslot() == P25P1Message.TIMESLOT_2) + //If we have a tracked event, update it. Otherwise, make sure we have the traffic channel allocated + if(tracker != null && tracker.isSameCallCheckingToOnly(ic, timestamp)) { - event = mTS2ChannelGrantEventMap.get(channel.getDownlinkFrequency()); - // If the event is stale, remove it and allow a new event to be created. - if(isStaleEvent(event, timestamp)) + //Only rebroadcast the tracked event if the timestamp was updated from this control channel timestamp + //Once the traffic channel takes over updating the tracked event time/duration, further control channel + // updates are ignored. + if(tracker.updateDurationControl(timestamp)) { - event = null; - mTS2ChannelGrantEventMap.remove(channel.getDownlinkFrequency()); + broadcast(tracker); } } else { - event = mTS1ChannelGrantEventMap.get(channel.getDownlinkFrequency()); - // If the event is stale, remove it and allow a new event to be created. - if(isStaleEvent(event, timestamp)) - { - event = null; - mTS1ChannelGrantEventMap.remove(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 - { - processP1ChannelGrant(channel, serviceOptions, ic, opcode, timestamp); + //Remove the existing call tracker because it's a different call now + removeTracker(channel.getDownlinkFrequency(), channel.getTimeslot()); + processP1ControlDirectedChannelGrant(channel, serviceOptions, ic, opcode, timestamp, context); } } finally @@ -838,102 +860,121 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I } /** - * Closes the call event for the specified channel frequency. + * Marks the tracked traffic channel event as complete but does not remove the event from the tracker map so + * that we can continue to track the event for the traffic channel. * @param frequency for the channel + * @return true if the tracked call event was marked as complete. */ - public void closeP1CallEvent(long frequency, long timestamp) + public boolean processP1TrafficCallEnd(long frequency, long timestamp, String context) { + boolean completed = false; + mLock.lock(); try { - DecodeEvent event = mTS1ChannelGrantEventMap.remove(frequency); + P25TrafficChannelEventTracker tracker = getTracker(frequency, P25P1Message.TIMESLOT_1); - if(event != null) + //If we have a tracker that we can mark complete, broadcast the updated tracker/event. + if(tracker != null && tracker.isStarted() && tracker.completeTraffic(timestamp)) { - event.end(timestamp); - broadcast(event); + completed = true; + broadcast(tracker); } } finally { mLock.unlock(); } + + return completed; } /** - * Indicates if the event is stale and should be replaced with a new decode event. Compares the event down time - * to the reference timestamp argument and indicates if that staleness exceeds a staleness threshold (2 seconds). + * Sends a channel start request to the ChannelProcessingManager. * - * Note: some decode events can remain in the channel grant event map if a traffic channel is not allocated for the - * event (ie tuner unavailable). The control channel only tells us that the traffic channel is active, but doesn't - * tell us when the traffic channel is torn down. So, we use this staleness evaluation to remove stale events that - * are left over from a previous traffic channel allocation. + * Note: this method is not thread safe and the calling method must protect access using mLock. * - * @param decodeEvent to evaluate - * @param timestamp for reference of event staleness. - * @return true if the event is stale. + * @param trafficChannel to use for the traffic channel + * @param apco25Channel that describes the traffic channel downlink frequency + * @param identifierCollection containing identifiers for the call + * @param timestamp of the request event */ - private static boolean isStaleEvent(DecodeEvent decodeEvent, long timestamp) + private void requestTrafficChannelStart(Channel trafficChannel, APCO25Channel apco25Channel, + IdentifierCollection identifierCollection, long timestamp) { - return decodeEvent != null && (timestamp - decodeEvent.getTimeEnd() > STALE_EVENT_THRESHOLD_MS); + if(getInterModuleEventBus() != null) + { + SourceConfigTuner sourceConfig = new SourceConfigTuner(); + sourceConfig.setFrequency(apco25Channel.getDownlinkFrequency()); + if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner) + { + sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner()); + } + trafficChannel.setSourceConfiguration(sourceConfig); + + //If this is a phase2 request and we have scramble/randomizer parameters, set them + if(mPhase2ScrambleParameters != null && trafficChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase2 p2) + { + p2.setScrambleParameters(mPhase2ScrambleParameters.copy()); + } + + mAllocatedTrafficChannelMap.put(apco25Channel.getDownlinkFrequency(), trafficChannel); + + ChannelStartProcessingRequest startChannelRequest = new ChannelStartProcessingRequest(trafficChannel, + apco25Channel, identifierCollection, this); + startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection, timestamp)); + startChannelRequest.addPreloadDataContent(new P25FrequencyBandPreloadDataContent(mFrequencyBandMap.values())); + getInterModuleEventBus().post(startChannelRequest); + } } /** - * Processes Phase 1 channel grants to allocate traffic channels and track overall channel usage. Generates - * decode events for each new channel that is allocated. + * Processes Phase 1 control-only channel grants to allocate traffic channels and track overall channel usage. + * Generates a tracked decode event for each new channel that is allocated. * - * Note: protected thread access to this method is controlled by the calling method. + * Note: this method is not thread safe and the calling method must protect access using mLock. * * @param apco25Channel for the traffic channel * @param serviceOptions for the traffic channel - optional can be null - * @param identifierCollection associated with the channel grant + * @param ic associated with the channel grant * @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, DecodeEventType decodeEventType, - boolean isDataChannelGrant, long timestamp) + private void processPhase1ControlChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, + IdentifierCollection ic, DecodeEventType decodeEventType, + boolean isDataChannelGrant, long timestamp, String context) { long frequency = apco25Channel.getDownlinkFrequency(); - P25ChannelGrantEvent event = mTS1ChannelGrantEventMap.get(frequency); + P25TrafficChannelEventTracker tracker = getTrackerRemoveIfStale(frequency, P25P1Message.TIMESLOT_1, timestamp); - // If the event is stale, remove it and allow a new event to be created. - if(isStaleEvent(event, timestamp)) + if(tracker != null && tracker.isSameCallCheckingToOnly(ic, timestamp)) { - event = null; - mTS1ChannelGrantEventMap.remove(frequency); - } + Identifier from = ic.getFromIdentifier(); - if(event != null && isSameCallUpdate(identifierCollection, event.getIdentifierCollection())) - { - Identifier from = identifierCollection.getFromIdentifier(); - - if(from != null) + if(from != null && tracker.isDifferentTalker(from)) { - Identifier currentFrom = event.getIdentifierCollection().getFromIdentifier(); - if(currentFrom != null && !from.equals(currentFrom)) - { - event.end(timestamp); - broadcast(event); + P25ChannelGrantEvent event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + .channelDescriptor(apco25Channel) + .details("CONTINUE - PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) + .identifiers(ic) + .build(); - event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) - .channelDescriptor(apco25Channel) - .details("CONTINUE - PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) - .identifiers(identifierCollection) - .build(); - - mTS1ChannelGrantEventMap.put(frequency, event); - } + tracker = new P25TrafficChannelEventTracker(event); + addTracker(tracker, frequency, P25P1Message.TIMESLOT_1); } - //update the ending timestamp so that the duration value is correctly calculated - event.update(timestamp); - broadcast(event); + //The tracked event can have an empty FROM identifier at start of call ... update here + tracker.addIdentifierIfMissing(from); - //Even though we have an event, the initial channel grant may have been rejected. Check to see if there + //update the ending timestamp so that the duration value is correctly calculated + tracker.updateDurationControl(timestamp); + + broadcast(tracker); + + //Even though we have a tracked 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 && isDataChannelGrant)) { @@ -943,58 +984,49 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I { if(isDataChannelGrant) { - event.setDetails("PHASE 1 DATA CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); + tracker.setDetails("PHASE 1 DATA CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); } else { - event.setDetails("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); + tracker.setDetails("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); } - event.setChannelDescriptor(apco25Channel); - broadcast(event); - SourceConfigTuner sourceConfig = new SourceConfigTuner(); - sourceConfig.setFrequency(frequency); - if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner) - { - sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner()); - } - trafficChannel.setSourceConfiguration(sourceConfig); - mAllocatedTrafficChannelMap.put(frequency, trafficChannel); + tracker.addChannelDescriptorIfMissing(apco25Channel); + broadcast(tracker); - ChannelStartProcessingRequest startChannelRequest = new ChannelStartProcessingRequest(trafficChannel, - apco25Channel, identifierCollection, this); - startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection, timestamp)); - startChannelRequest.addPreloadDataContent(new P25FrequencyBandPreloadDataContent(mFrequencyBandMap.values())); - getInterModuleEventBus().post(startChannelRequest); + requestTrafficChannelStart(trafficChannel, apco25Channel, ic, timestamp); + } + else + { + tracker.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED); } } return; } - if(mIgnoreDataCalls && isDataChannelGrant && event == null) + if(mIgnoreDataCalls && isDataChannelGrant && tracker == null) { - event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + P25ChannelGrantEvent event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channelDescriptor(apco25Channel) .details("IGNORED: PHASE 1 DATA CALL " + (serviceOptions != null ? serviceOptions : "")) - .identifiers(identifierCollection) + .identifiers(ic) .build(); - mTS1ChannelGrantEventMap.put(frequency, event); - broadcast(event); + tracker = new P25TrafficChannelEventTracker(event); + addTracker(tracker, frequency, P25P1Message.TIMESLOT_1); + broadcast(tracker); return; } - event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + String details = isDataChannelGrant ? "PHASE 1 DATA CHANNEL GRANT " : "PHASE 1 CHANNEL GRANT " + + (serviceOptions != null ? serviceOptions : ""); + + P25ChannelGrantEvent event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channelDescriptor(apco25Channel) - .details("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) - .identifiers(identifierCollection) + .details(details) + .identifiers(ic) .build(); - - if(isDataChannelGrant) - { - event.setDetails("PHASE 1 DATA CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); - } - - mTS1ChannelGrantEventMap.put(frequency, event); + tracker = new P25TrafficChannelEventTracker(event); + addTracker(tracker, frequency, P25P1Message.TIMESLOT_1); //Allocate a traffic channel for the downlink frequency if one isn't already allocated if(!mAllocatedTrafficChannelMap.containsKey(frequency)) @@ -1003,126 +1035,67 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I if(trafficChannel == null) { - event.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - " + (event.getDetails() != null ? event.getDetails() : "")); + tracker.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - " + (event.getDetails() != null ? event.getDetails() : "")); return; } - SourceConfigTuner sourceConfig = new SourceConfigTuner(); - sourceConfig.setFrequency(frequency); - if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner) - { - sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner()); - } - trafficChannel.setSourceConfiguration(sourceConfig); - mAllocatedTrafficChannelMap.put(frequency, trafficChannel); - - ChannelStartProcessingRequest 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); - } + requestTrafficChannelStart(trafficChannel, apco25Channel, ic, timestamp); } - broadcast(event); + broadcast(tracker); } /** - * Processes Phase 2 channel grants to allocate traffic channels and track overall channel usage. Generates - * decode events for each new channel that is allocated. + * Processes Phase 2 channel grants from both the control channel and from traffic channels 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. + * Note: this method is not thread safe and the calling method must protect access using mLock. * * @param apco25Channel for the traffic channel * @param serviceOptions for the traffic channel - optional can be null - * @param identifierCollection associated with the channel grant + * @param ic associated with the channel grant * @param decodeEventType to use for the event. * @param isDataChannelGrant indicator if this is a data channel grant * @param timestamp for the event */ - private DecodeEvent processPhase2ChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, - IdentifierCollection identifierCollection, DecodeEventType decodeEventType, + private void processPhase2ChannelGrant(APCO25Channel apco25Channel, ServiceOptions serviceOptions, + IdentifierCollection ic, DecodeEventType decodeEventType, boolean isDataChannelGrant, long timestamp) { - if(mPhase2ScrambleParameters != null && identifierCollection instanceof MutableIdentifierCollection) + if(mPhase2ScrambleParameters != null && ic instanceof MutableIdentifierCollection) { - ((MutableIdentifierCollection)identifierCollection).silentUpdate(ScrambleParameterIdentifier.create(mPhase2ScrambleParameters)); + ((MutableIdentifierCollection)ic).silentUpdate(ScrambleParameterIdentifier.create(mPhase2ScrambleParameters)); } int timeslot = apco25Channel.getTimeslot(); long frequency = apco25Channel.getDownlinkFrequency(); + ic.setTimeslot(timeslot); - P25ChannelGrantEvent event = null; + P25TrafficChannelEventTracker tracker = getTrackerRemoveIfStale(apco25Channel, timestamp); - if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) + if(tracker != null && tracker.isSameCallCheckingToOnly(ic, timestamp)) { - event = mTS1ChannelGrantEventMap.get(frequency); - // If the event is stale, remove it and allow a new event to be created. - if(isStaleEvent(event, timestamp)) + Identifier from = ic.getFromIdentifier(); + + if(from != null && tracker.isDifferentTalker(from)) { - event = null; - mTS1ChannelGrantEventMap.remove(frequency); - } - } - else if(timeslot == P25P1Message.TIMESLOT_2) - { - event = mTS2ChannelGrantEventMap.get(frequency); - // If the event is stale, remove it and allow a new event to be created. - if(isStaleEvent(event, timestamp)) - { - event = null; - mTS2ChannelGrantEventMap.remove(frequency); - } - } - else - { - mLog.error("Ignoring: Invalid timeslot [" + timeslot + "] detected for P25 Phase 2 Channel Grant."); - return event; - } + P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + .channelDescriptor(apco25Channel) + .details("CONTINUE - PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) + .identifiers(ic) + .timeslot(apco25Channel.getTimeslot()) + .build(); - identifierCollection.setTimeslot(timeslot); - - if(event != null && isSameCallUpdate(identifierCollection, event.getIdentifierCollection())) - { - Identifier from = identifierCollection.getFromIdentifier(); - - if(from != null) - { - Identifier currentFrom = event.getIdentifierCollection().getFromIdentifier(); - if(currentFrom != null && !from.equals(currentFrom)) - { - event.end(timestamp); - - P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) - .channelDescriptor(apco25Channel) - .details("CONTINUE - PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) - .identifiers(identifierCollection) - .build(); - - if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) - { - mTS1ChannelGrantEventMap.put(frequency, continuationGrantEvent); - } - else - { - mTS2ChannelGrantEventMap.put(frequency, continuationGrantEvent); - } - - broadcast(continuationGrantEvent); - } + tracker = new P25TrafficChannelEventTracker(continuationGrantEvent); + addTracker(tracker, frequency, timeslot); + broadcast(tracker); } //update the ending timestamp so that the duration value is correctly calculated - event.update(timestamp); + tracker.updateDurationControl(timestamp); + broadcast(tracker); - //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 + //Even though we have a tracked 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 && isDataChannelGrant) && (getCurrentControlFrequency() != frequency)) @@ -1131,64 +1104,44 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I if(trafficChannel != null) { - event.setDetails("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); - event.setChannelDescriptor(apco25Channel); - broadcast(event); - SourceConfigTuner sourceConfig = new SourceConfigTuner(); - sourceConfig.setFrequency(frequency); - if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner) - { - sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner()); - } - trafficChannel.setSourceConfiguration(sourceConfig); - mAllocatedTrafficChannelMap.put(frequency, trafficChannel); - - //If we have valid scramble/randomizer parameters, set them in the decode config - if(mPhase2ScrambleParameters != null) - { - DecodeConfigP25Phase2 decodeConfig = (DecodeConfigP25Phase2)trafficChannel.getDecodeConfiguration(); - decodeConfig.setScrambleParameters(mPhase2ScrambleParameters.copy()); - } - - ChannelStartProcessingRequest startChannelRequest = - new ChannelStartProcessingRequest(trafficChannel, apco25Channel, identifierCollection, this); - startChannelRequest.addPreloadDataContent(new PatchGroupPreLoadDataContent(identifierCollection, timestamp)); - startChannelRequest.addPreloadDataContent(new P25FrequencyBandPreloadDataContent(mFrequencyBandMap.values())); - getInterModuleEventBus().post(startChannelRequest); + tracker.setDetails("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")); + tracker.addChannelDescriptorIfMissing(apco25Channel); + broadcast(tracker); + requestTrafficChannelStart(trafficChannel, apco25Channel, ic, timestamp); + } + else + { + tracker.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED); } } - return event; + return; } - if(mIgnoreDataCalls && isDataChannelGrant && event == null) + if(mIgnoreDataCalls && isDataChannelGrant && tracker == null) { - event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + P25ChannelGrantEvent event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channelDescriptor(apco25Channel) .details("PHASE 2 DATA CALL IGNORED: " + (serviceOptions != null ? serviceOptions : "")) - .identifiers(identifierCollection) + .identifiers(ic) + .timeslot(apco25Channel.getTimeslot()) .build(); - //Phase 1 events get stored in TS1 only - mTS1ChannelGrantEventMap.put(frequency, event); - broadcast(event); - return event; + tracker = new P25TrafficChannelEventTracker(event); + addTracker(tracker, frequency, P25P1Message.TIMESLOT_1); + broadcast(tracker); + return; } - event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) + P25ChannelGrantEvent event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions) .channelDescriptor(apco25Channel) .details("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : "")) - .identifiers(identifierCollection) + .identifiers(ic) + .timeslot(apco25Channel.getTimeslot()) .build(); - if(timeslot == P25P1Message.TIMESLOT_0 || timeslot == P25P1Message.TIMESLOT_1) - { - mTS1ChannelGrantEventMap.put(frequency, event); - } - else - { - mTS2ChannelGrantEventMap.put(frequency, event); - } + tracker = new P25TrafficChannelEventTracker(event); + addTracker(tracker, frequency, timeslot); //Allocate a traffic channel for the downlink frequency if one isn't already allocated if(!mAllocatedTrafficChannelMap.containsKey(frequency) && frequency != getCurrentControlFrequency()) @@ -1197,34 +1150,15 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I if(trafficChannel == null) { - event.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - IGNORED"); - return event; + tracker.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - IGNORED"); } - - SourceConfigTuner sourceConfig = new SourceConfigTuner(); - sourceConfig.setFrequency(frequency); - if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner) + else { - sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner()); + requestTrafficChannelStart(trafficChannel, apco25Channel, ic, timestamp); } - trafficChannel.setSourceConfiguration(sourceConfig); - mAllocatedTrafficChannelMap.put(frequency, trafficChannel); - - ChannelStartProcessingRequest startChannelRequest = - 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(event); - return event; + broadcast(tracker); } /** @@ -1236,7 +1170,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I */ private DecodeEventType getEventType(MacOpcode macOpcode, ServiceOptions serviceOptions, DecodeEventType current) { - boolean encrypted = serviceOptions != null ? serviceOptions.isEncrypted() : false; + boolean encrypted = serviceOptions != null && serviceOptions.isEncrypted(); DecodeEventType type = null; @@ -1312,48 +1246,33 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I 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; + boolean encrypted = serviceOptions != null && serviceOptions.isEncrypted(); DecodeEventType type = null; if(opcode != null) { - switch(opcode) + type = 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; - } + case OSP_GROUP_VOICE_CHANNEL_GRANT, OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE, + OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT -> + encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP; + case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT, OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE -> + encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT; + case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT, + OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE -> + encrypted ? DecodeEventType.CALL_INTERCONNECT_ENCRYPTED : DecodeEventType.CALL_INTERCONNECT; + case OSP_SNDCP_DATA_CHANNEL_GRANT, OSP_GROUP_DATA_CHANNEL_GRANT, OSP_INDIVIDUAL_DATA_CHANNEL_GRANT -> + encrypted ? DecodeEventType.DATA_CALL_ENCRYPTED : DecodeEventType.DATA_CALL; + case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT, MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE -> + encrypted ? DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED : DecodeEventType.CALL_PATCH_GROUP; + default -> type; + }; } if(type == null) @@ -1416,59 +1335,6 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I mChannelEventListener = null; } - /** - * 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 existingIC containing a TO identifier - * @param nextIC containing a TO identifier - * @return true if both collections contain a TO identifier and the TO identifiers are the same value - */ - private boolean isSameCallUpdate(IdentifierCollection existingIC, IdentifierCollection nextIC) - { - Identifier existingTO = existingIC.getToIdentifier(); - Identifier nextTO = nextIC.getToIdentifier(); - return existingTO != null && existingTO.equals(nextTO); - } - - /** - * 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 existingIC for the existing/previous event containing a TO identifier - * @param nextIC for the next event containing a TO identifier - * @return true if both collections contain a TO identifier and the TO identifiers are the same value - */ - private boolean isSameCallFull(IdentifierCollection existingIC, IdentifierCollection nextIC) - { - Identifier existingTO = existingIC.getToIdentifier(); - Identifier nextTO = nextIC.getToIdentifier(); - - if(existingTO != null && existingTO.equals(nextTO)) - { - Identifier existingFROM = existingIC.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(existingFROM == null || existingFROM.getForm() == Form.TALKER_ALIAS) - { - return true; - } - - Identifier nextFROM = nextIC.getFromIdentifier(); - - if(nextFROM != null && nextFROM.getForm() == Form.TALKER_ALIAS) - { - return true; - } - - return existingFROM.equals(nextFROM); - } - - return false; - } - /** * Implements the IDecodeEventProvider interface to provide channel events to an external listener. */ @@ -1530,13 +1396,13 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I mMessageListener = message -> { if(mPhase2ScrambleParameters == null && message.isValid()) { - if(message instanceof NetworkStatusBroadcast) + if(message instanceof NetworkStatusBroadcast nsb) { - mPhase2ScrambleParameters = ((NetworkStatusBroadcast)message).getScrambleParameters(); + mPhase2ScrambleParameters = nsb.getScrambleParameters(); } - else if(message instanceof AMBTCNetworkStatusBroadcast) + else if(message instanceof AMBTCNetworkStatusBroadcast nsb) { - mPhase2ScrambleParameters = ((AMBTCNetworkStatusBroadcast)message).getScrambleParameters(); + mPhase2ScrambleParameters = nsb.getScrambleParameters(); } } }; @@ -1603,7 +1469,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I .findFirst() .ifPresent(frequency -> { mAllocatedTrafficChannelMap.remove(frequency); - mTS1ChannelGrantEventMap.remove(frequency); + removeTracker(frequency, P25P1Message.TIMESLOT_1); mAvailablePhase1TrafficChannelQueue.add(channel); }); break; @@ -1618,14 +1484,14 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I //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); + P25TrafficChannelEventTracker tracker = getTracker(rejectedFrequency, P25P1Message.TIMESLOT_1); - if(event != null && !event.getDetails().contains(CHANNEL_START_REJECTED)) + if(tracker != null && !tracker.getEvent().getDetails().contains(CHANNEL_START_REJECTED)) { - event.setDetails(CHANNEL_START_REJECTED + " " + channelEvent.getDescription() + - (event.getDetails() != null ? " - " + event.getDetails() : "")); + tracker.setDetails(CHANNEL_START_REJECTED + " " + channelEvent.getDescription() + + (tracker.getEvent().getDetails() != null ? " - " + tracker.getEvent().getDetails() : "")); } - broadcast(event); + broadcast(tracker); }); break; } @@ -1652,7 +1518,9 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I .ifPresent(frequency -> { mAllocatedTrafficChannelMap.remove(frequency); mAvailablePhase2TrafficChannelQueue.add(channel); - mTS1ChannelGrantEventMap.remove(frequency); + //Remove tracker from both timeslots + removeTracker(frequency, P25P1Message.TIMESLOT_1); + removeTracker(frequency, P25P1Message.TIMESLOT_2); }); break; case NOTIFICATION_PROCESSING_START_REJECTED: @@ -1664,20 +1532,20 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I mAllocatedTrafficChannelMap.remove(rejectedFrequency); mAvailablePhase1TrafficChannelQueue.add(channel); - //Leave the event in the map so that it doesn't get recreated. The channel + //Leave the tracked 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) + P25TrafficChannelEventTracker tracker = getTracker(rejectedFrequency, P25P1Message.TIMESLOT_1); + if (tracker != null) { - broadcast(event1); + broadcast(tracker); } - //Leave the event in the map so that it doesn't get recreated. The channel + //Leave the tracked 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) + P25TrafficChannelEventTracker tracker2 = getTracker(rejectedFrequency, P25P1Message.TIMESLOT_2); + if (tracker2 != null) { - broadcast(event2); + broadcast(tracker2); } }); break; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25Channel.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25Channel.java index da34f148..17fe2b74 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25Channel.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/channel/APCO25Channel.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; @@ -112,6 +109,36 @@ public class APCO25Channel extends Identifier implements IChannelDes return new APCO25Channel(new P25Channel(frequencyBand, channelNumber)); } + /** + * Creates a new APCO-25 identifier with the same frequencyBand, and a different channelNumber representing the + * requested timeslot. + * @param requestedTimeslot to decorate as. + * @return decorated channel. + */ + public APCO25Channel decorateAs(int requestedTimeslot) + { + if(getTimeslot() == requestedTimeslot) + { + return this; + } + + P25Channel existing = getValue(); + + int channelNumber = existing.getChannelNumber(); + if(requestedTimeslot == 1) + { + channelNumber--; + } + else + { + channelNumber++; + } + + P25Channel decoratedChannel = new P25Channel(existing.getBandIdentifier(), channelNumber); + decoratedChannel.setFrequencyBand(existing.getFrequencyBand()); + return new APCO25Channel(decoratedChannel); + } + @Override public boolean equals(Object o) { 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 8661f14d..90c582b1 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 @@ -42,6 +42,22 @@ public class P25Channel implements IChannelDescriptor return Protocol.APCO25; } + /** + * Band identifier for this channel. + */ + public int getBandIdentifier() + { + return mBandIdentifier; + } + + /** + * Channel number for this channel. + */ + public int getChannelNumber() + { + return mChannelNumber; + } + /** * Frequency band information that backs this channel */ 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 8d617fd1..20fb940d 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 @@ -66,15 +66,12 @@ 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.l3harris.LCHarrisTalkerAliasComplete; 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.motorola.MotorolaTalkerAliasComplete; 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; @@ -346,43 +343,42 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList mTrafficChannelManager.getTalkerAliasManager().update(radioIdentifier, talkerAlias.getTalkerAlias()); } - mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), talkerAlias.getTalkerAlias(), talkerAlias.getTimestamp()); + mTrafficChannelManager.processP1TrafficCurrentUser(getCurrentFrequency(), talkerAlias.getTalkerAlias(), talkerAlias.getTimestamp(), talkerAlias.toString()); } /** - * Commands the traffic channel manager to process a traffic channel grant and allocate a decoder to process the - * traffic channel. - * @param apco25Channel to allocate + * Processes a control channel directed traffic channel grant. + * @param channel to allocate * @param serviceOptions for 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, - List identifiers, Opcode opcode, long timestamp) + private void processControlTrafficGrant(APCO25Channel channel, ServiceOptions serviceOptions, + List identifiers, Opcode opcode, long timestamp, String context) { - if(apco25Channel.getValue().getDownlinkFrequency() > 0) + if(channel.getValue().getDownlinkFrequency() > 0) { MutableIdentifierCollection mic = getMutableIdentifierCollection(identifiers, timestamp); mTrafficChannelManager.getTalkerAliasManager().enrichMutable(mic); - mTrafficChannelManager.processP1ChannelGrant(apco25Channel, serviceOptions, mic, opcode, timestamp); + mTrafficChannelManager.processP1ControlDirectedChannelGrant(channel, serviceOptions, mic, opcode, timestamp, context); } } /** - * Process an update for another channel and send it to the traffic channel manager. + * Processes a control channel announced update to a traffic channel * @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) + private void processControlAnnouncedTrafficUpdate(APCO25Channel channel, ServiceOptions serviceOptions, + List identifiers, Opcode opcode, long timestamp, String context) { MutableIdentifierCollection mic = getMutableIdentifierCollection(identifiers, timestamp); mTrafficChannelManager.getTalkerAliasManager().enrichMutable(mic); - mTrafficChannelManager.processP1ChannelUpdate(channel, serviceOptions, mic, opcode, timestamp); + mTrafficChannelManager.processP1ControlAnnouncedTrafficUpdate(channel, serviceOptions, mic, opcode, timestamp, context); } /** @@ -434,8 +430,9 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList } mTrafficChannelManager.getTalkerAliasManager().enrichMutable(getIdentifierCollection()); - mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), getCurrentChannel(), decodeEventType, - serviceOptions, getIdentifierCollection(), timestamp, null ); + MutableIdentifierCollection mic = getMutableIdentifierCollection(getIdentifierCollection().getIdentifiers(), timestamp); + mTrafficChannelManager.processP1TrafficCurrentUser(getCurrentFrequency(), getCurrentChannel(), decodeEventType, + serviceOptions, mic, timestamp, null, lcw.toString()); if(serviceOptions.isEncrypted()) { @@ -760,15 +757,15 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList 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()); + processControlAnnouncedTrafficUpdate(upd.getChannel(), upd.getServiceOptions(), upd.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp(), ambtc.toString()); } 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()); + processControlAnnouncedTrafficUpdate(upd.getChannel(), upd.getServiceOptions(), upd.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp(), ambtc.toString()); } break; } @@ -784,64 +781,59 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case OSP_GROUP_DATA_CHANNEL_GRANT: if(ambtc instanceof AMBTCGroupDataChannelGrant gdcg) { - processChannelGrant(gdcg.getChannel(), gdcg.getServiceOptions(), gdcg.getIdentifiers(), - ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + processControlTrafficGrant(gdcg.getChannel(), gdcg.getServiceOptions(), gdcg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp(), ambtc.toString()); } break; case OSP_GROUP_VOICE_CHANNEL_GRANT: if(ambtc instanceof AMBTCGroupVoiceChannelGrant gvcg) { - processChannelGrant(gvcg.getChannel(), gvcg.getServiceOptions(), gvcg.getIdentifiers(), - ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + processControlTrafficGrant(gvcg.getChannel(), gvcg.getServiceOptions(), gvcg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp(), ambtc.toString()); } break; case OSP_INDIVIDUAL_DATA_CHANNEL_GRANT: if(ambtc instanceof AMBTCIndividualDataChannelGrant idcg) { - processChannelGrant(idcg.getChannel(), idcg.getServiceOptions(), idcg.getIdentifiers(), - ambtc.getHeader().getOpcode(), ambtc.getTimestamp()); + processControlTrafficGrant(idcg.getChannel(), idcg.getServiceOptions(), idcg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp(), ambtc.toString()); } 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()); + processControlTrafficGrant(ticg.getChannel(), ticg.getServiceOptions(), ticg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp(), ambtc.toString()); } 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()); + processControlTrafficGrant(uuvscg.getChannel(), uuvscg.getServiceOptions(), uuvscg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp(), ambtc.toString()); } 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()); + processControlTrafficGrant(mgrcg.getChannel(), mgrcg.getServiceOptions(), mgrcg.getIdentifiers(), + ambtc.getHeader().getOpcode(), ambtc.getTimestamp(), ambtc.toString()); } break; } } /** - * Processes a Header Data Unit message and starts a new call event. + * Processes a Header Data Unit message */ private void processHDU(IMessage message) { if(message.isValid() && message instanceof HDUMessage hdu) { 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.getTalkerAliasManager().enrichMutable(mic); - mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), getCurrentChannel(), type, - serviceOptions, mic, message.getTimestamp(), details); + //Run the talkgroup through the patch group manager so we don't get a plain talkgroup in addition to the patch + Identifier talkgroup = mPatchGroupManager.update(headerData.getTalkgroup(), message.getTimestamp()); + mTrafficChannelManager.processP1TrafficCurrentUser(getCurrentFrequency(), talkgroup, hdu.getTimestamp(), message.toString()); if(headerData.isEncryptedAudio()) { @@ -881,24 +873,19 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(esp.isEncryptedAudio()) { getIdentifierCollection().update(esp.getIdentifiers()); - mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), esp.getEncryptionKey(), - message.getTimestamp()); + mTrafficChannelManager.processP1TrafficCurrentUser(getCurrentFrequency(), esp.getEncryptionKey(), + message.getTimestamp(), ldu2.toString()); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED)); } else { getIdentifierCollection().remove(Form.ENCRYPTION_KEY); - mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), null, - message.getTimestamp()); + mTrafficChannelManager.processP1TrafficCurrentUser(getCurrentFrequency(), null, + message.getTimestamp(), ldu2.toString()); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); } } - else - { - mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), null, message.getTimestamp()); - } } - } /** @@ -906,8 +893,12 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList */ private void processTDU(P25P1Message message) { - mTrafficChannelManager.closeP1CallEvent(getCurrentFrequency(), message.getTimestamp()); - getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); + //If the TCM indicates that it closed the traffic call event, then remove the FROM users + if(mTrafficChannelManager.processP1TrafficCallEnd(getCurrentFrequency(), message.getTimestamp(), "TDU:" + message)) + { + getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); + } + broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); } @@ -919,8 +910,12 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList */ private void processTDULC(P25P1Message message) { - mTrafficChannelManager.closeP1CallEvent(getCurrentFrequency(), message.getTimestamp()); - getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); + boolean completed = mTrafficChannelManager.processP1TrafficCallEnd(getCurrentFrequency(), message.getTimestamp(), "TDULC:" + message); + + if(completed) + { + getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM); + } if(message instanceof TDULinkControlMessage tdulc) { @@ -931,6 +926,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList //Set the state to ACTIVE while the call continues in hangtime. The processLC() method will signal // the channel teardown when that happens. broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); + //Don't signal call end to the TCM or remove FROM identifiers unless the call has started on this traffic channel. processLC(lcw, message.getTimestamp(), true); } } @@ -1724,48 +1720,48 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE: if(tsbk instanceof MotorolaGroupRegroupChannelUpdate pgvcgu) { - processChannelUpdate(pgvcgu.getChannel1(), null, Collections.singletonList(pgvcgu.getPatchGroup1()), - tsbk.getOpcode(), pgvcgu.getTimestamp()); + processControlAnnouncedTrafficUpdate(pgvcgu.getChannel1(), null, Collections.singletonList(pgvcgu.getPatchGroup1()), + tsbk.getOpcode(), pgvcgu.getTimestamp(), tsbk.toString()); if(pgvcgu.hasPatchGroup2()) { - processChannelUpdate(pgvcgu.getChannel2(), null, Collections.singletonList(pgvcgu.getPatchGroup2()), - tsbk.getOpcode(), pgvcgu.getTimestamp()); + processControlAnnouncedTrafficUpdate(pgvcgu.getChannel2(), null, Collections.singletonList(pgvcgu.getPatchGroup2()), + tsbk.getOpcode(), pgvcgu.getTimestamp(), tsbk.toString()); } } 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()); + processControlAnnouncedTrafficUpdate(gvcgu.getChannelA(), null, Collections.singletonList(gvcgu.getGroupAddressA()), + tsbk.getOpcode(), gvcgu.getTimestamp(), tsbk.toString()); if(gvcgu.hasGroupB()) { - processChannelGrant(gvcgu.getChannelB(), null, Collections.singletonList(gvcgu.getGroupAddressB()), - tsbk.getOpcode(), gvcgu.getTimestamp()); + processControlAnnouncedTrafficUpdate(gvcgu.getChannelB(), null, Collections.singletonList(gvcgu.getGroupAddressB()), + tsbk.getOpcode(), gvcgu.getTimestamp(), tsbk.toString()); } } 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()); + processControlAnnouncedTrafficUpdate(gvcgue.getChannel(), gvcgue.getServiceOptions(), gvcgue.getIdentifiers(), + tsbk.getOpcode(), gvcgue.getTimestamp(), tsbk.toString()); } 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()); + processControlAnnouncedTrafficUpdate(tivcgu.getChannel(), tivcgu.getServiceOptions(), tivcgu.getIdentifiers(), + tsbk.getOpcode(), tivcgu.getTimestamp(), tsbk.toString()); } 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()); + processControlAnnouncedTrafficUpdate(uuvcgu.getChannel(), null, uuvcgu.getIdentifiers(), tsbk.getOpcode(), + uuvcgu.getTimestamp(), tsbk.toString()); } break; } @@ -1781,43 +1777,43 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT: if(tsbk instanceof MotorolaGroupRegroupChannelGrant mgrcg) { - processChannelGrant(mgrcg.getChannel(), mgrcg.getServiceOptions(), mgrcg.getIdentifiers(), tsbk.getOpcode(), - mgrcg.getTimestamp()); + processControlTrafficGrant(mgrcg.getChannel(), mgrcg.getServiceOptions(), mgrcg.getIdentifiers(), tsbk.getOpcode(), + mgrcg.getTimestamp(), tsbk.toString()); } break; case OSP_GROUP_DATA_CHANNEL_GRANT: if(tsbk instanceof GroupDataChannelGrant gdcg) { - processChannelGrant(gdcg.getChannel(), gdcg.getDataServiceOptions(), gdcg.getIdentifiers(), - tsbk.getOpcode(), gdcg.getTimestamp()); + processControlTrafficGrant(gdcg.getChannel(), gdcg.getDataServiceOptions(), gdcg.getIdentifiers(), + tsbk.getOpcode(), gdcg.getTimestamp(), tsbk.toString()); } break; case OSP_GROUP_VOICE_CHANNEL_GRANT: if(tsbk instanceof GroupVoiceChannelGrant gvcg) { - processChannelGrant(gvcg.getChannel(), gvcg.getServiceOptions(), gvcg.getIdentifiers(), tsbk.getOpcode(), - gvcg.getTimestamp()); + processControlTrafficGrant(gvcg.getChannel(), gvcg.getServiceOptions(), gvcg.getIdentifiers(), tsbk.getOpcode(), + gvcg.getTimestamp(), tsbk.toString()); } break; case OSP_SNDCP_DATA_CHANNEL_GRANT: if(tsbk instanceof SNDCPDataChannelGrant dcg) { - processChannelGrant(dcg.getChannel(), dcg.getServiceOptions(), dcg.getIdentifiers(), tsbk.getOpcode(), - dcg.getTimestamp()); + processControlTrafficGrant(dcg.getChannel(), dcg.getServiceOptions(), dcg.getIdentifiers(), tsbk.getOpcode(), + dcg.getTimestamp(), tsbk.toString()); } break; case OSP_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: if(tsbk instanceof UnitToUnitVoiceChannelGrant uuvcg) { - processChannelGrant(uuvcg.getChannel(), null, uuvcg.getIdentifiers(), tsbk.getOpcode(), - uuvcg.getTimestamp()); + processControlTrafficGrant(uuvcg.getChannel(), null, uuvcg.getIdentifiers(), tsbk.getOpcode(), + uuvcg.getTimestamp(), tsbk.toString()); } break; case OSP_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: if(tsbk instanceof TelephoneInterconnectVoiceChannelGrant tivcg) { - processChannelGrant(tivcg.getChannel(), tivcg.getServiceOptions(), tivcg.getIdentifiers(), tsbk.getOpcode(), - tivcg.getTimestamp()); + processControlTrafficGrant(tivcg.getChannel(), tivcg.getServiceOptions(), tivcg.getIdentifiers(), tsbk.getOpcode(), + tivcg.getTimestamp(), tsbk.toString()); } break; } @@ -1843,7 +1839,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } else { @@ -1854,14 +1850,14 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(lcw instanceof LCMotorolaTalkComplete tc) { getIdentifierCollection().update(tc.getAddress()); - mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), tc.getAddress(), timestamp); - closeCurrentCallEvent(timestamp); + //Note: don't update the traffic channel manager because the current event is already in closeout + closeCurrentCallEvent(timestamp, lcw.toString()); } break; //Call termination case CALL_TERMINATION_OR_CANCELLATION: - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); //Note: we only broadcast an END state if this is a network-commanded channel teardown if(lcw instanceof LCCallTermination lcct && lcct.isNetworkCommandedTeardown()) @@ -1870,38 +1866,17 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList } break; - //Calls in-progress on another channel + //Voice Channel Update messages - indicates calls in-progress on another channel - ignored 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); - } + closeCurrentCallEvent(timestamp, lcw.toString()); } 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); + closeCurrentCallEvent(timestamp, lcw.toString()); } break; @@ -1923,7 +1898,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } mNetworkConfigurationMonitor.process(lcw); @@ -1945,7 +1920,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } mNetworkConfigurationMonitor.process(lcw); @@ -1968,7 +1943,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } mNetworkConfigurationMonitor.process(lcw); @@ -1990,7 +1965,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } mNetworkConfigurationMonitor.process(lcw); @@ -2006,7 +1981,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case SYSTEM_SERVICE_BROADCAST: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } mNetworkConfigurationMonitor.process(lcw); break; @@ -2019,23 +1994,17 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList mPatchGroupManager.removePatchGroups(lcw.getIdentifiers()); break; case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE: + //Voice Channel Update message - indicates calls in-progress on another channel - ignored if(isTerminator) { - closeCurrentCallEvent(timestamp); - } - - if(lcw instanceof LCMotorolaGroupRegroupVoiceChannelUpdate vcu) - { - MutableIdentifierCollection mic = getMutableIdentifierCollection(vcu.getSupergroupAddress(), timestamp); - mTrafficChannelManager.processP1ChannelUpdate(vcu.getChannel(), vcu.getServiceOptions(), mic, - null, timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } break; case MOTOROLA_TALKER_ALIAS_HEADER: case MOTOROLA_TALKER_ALIAS_DATA_BLOCK: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } break; @@ -2043,14 +2012,14 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case CALL_ALERT: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.PAGE, "Call Alert"); break; case EXTENDED_FUNCTION_COMMAND: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } if(lcw instanceof LCExtendedFunctionCommand efc) { @@ -2062,7 +2031,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case EXTENDED_FUNCTION_COMMAND_EXTENDED: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } if(lcw instanceof LCExtendedFunctionCommandExtended efce) { @@ -2072,14 +2041,14 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case GROUP_AFFILIATION_QUERY: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.QUERY, "Group Affiliation"); break; case MESSAGE_UPDATE: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } if(lcw instanceof LCMessageUpdate mu) { @@ -2090,7 +2059,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case MESSAGE_UPDATE_EXTENDED: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } if(lcw instanceof LCMessageUpdateExtended mue) { @@ -2101,14 +2070,14 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case STATUS_QUERY: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.QUERY, "Status"); break; case STATUS_UPDATE: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } if(lcw instanceof LCStatusUpdate su) { @@ -2119,7 +2088,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case STATUS_UPDATE_EXTENDED: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } if(lcw instanceof LCStatusUpdateExtended sue) { @@ -2130,7 +2099,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } if(lcw instanceof LCTelephoneInterconnectAnswerRequest tiar) { @@ -2141,21 +2110,21 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case UNIT_AUTHENTICATION_COMMAND: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.COMMAND, "Authenticate Unit"); break; case UNIT_REGISTRATION_COMMAND: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.COMMAND, "Unit Registration"); break; case UNIT_TO_UNIT_ANSWER_REQUEST: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } broadcastEvent(lcw.getIdentifiers(), timestamp, DecodeEventType.PAGE, "Unit-to-Unit Answer Request"); break; @@ -2174,7 +2143,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList case MOTOROLA_UNIT_GPS: if(lcw instanceof LCMotorolaUnitGPS gps) { - mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), gps.getLocation(), timestamp); + mTrafficChannelManager.processP1TrafficCurrentUser(getCurrentFrequency(), gps.getLocation(), timestamp, lcw.toString()); MutableIdentifierCollection mic = getMutableIdentifierCollection(gps.getIdentifiers(), timestamp); PlottableDecodeEvent event = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, timestamp) @@ -2190,7 +2159,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } break; case SOURCE_ID_EXTENSION: @@ -2199,7 +2168,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList default: if(isTerminator) { - closeCurrentCallEvent(timestamp); + closeCurrentCallEvent(timestamp, lcw.toString()); } // if(lcw.getVendor().isLoggable()) @@ -2215,11 +2184,11 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList /** * Closes the call event on the current channel. - * @param timestamp + * @param timestamp for the closure */ - private void closeCurrentCallEvent(long timestamp) + private void closeCurrentCallEvent(long timestamp, String context) { - mTrafficChannelManager.closeP1CallEvent(getCurrentFrequency(), timestamp); + mTrafficChannelManager.processP1TrafficCallEnd(getCurrentFrequency(), timestamp, context); getIdentifierCollection().remove(IdentifierClass.USER); } 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 796446ed..5035c585 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 @@ -151,7 +151,7 @@ public class LCGroupVoiceChannelUser extends VoiceLinkControlMessage implements { if(mSourceAddress == null) { - if(isExtensionRequired() && mSourceIdExtension != null) + if(isExtensionRequired() && mSourceIdExtension != null && mSourceIdExtension.isValidExtendedSource()) { mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(getInt(SOURCE_ADDRESS), mSourceIdExtension.getWACN(), mSourceIdExtension.getSystem(), mSourceIdExtension.getId()); 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 index 76f9c86a..2526e12f 100644 --- 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 @@ -65,6 +65,16 @@ public class LCSourceIDExtension extends LinkControlWord implements IExtendedSou return sb.toString(); } + /** + * Indicates that at least one of the WACN, System, or ID values are non-zero. + * @return + */ + public boolean isValidExtendedSource() + { + return getWACN() != 0 && getSystem() != 0 && getId() != 0; + } + + @Override public boolean isTerminator() { 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 b9628770..1b709f3a 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 @@ -38,7 +38,6 @@ 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; @@ -123,7 +122,7 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate //The Costas Loop receives symbol-inversion correction requests when detected. //The PLL gain monitor receives sync detect/loss signals from the message framer - mMessageFramer = new P25P2MessageFramer(mCostasLoop, DecoderType.P25_PHASE2.getProtocol().getBitRate()); + mMessageFramer = new P25P2MessageFramer(mCostasLoop); if(mDecodeConfigP25Phase2 !=null) { @@ -146,8 +145,6 @@ public class P25P2DecoderHDQPSK extends P25P2Decoder implements IdentifierUpdate @Override public void receive(ComplexSamples samples) { - mMessageFramer.setCurrentTime(System.currentTimeMillis()); - float[] i = mIBasebandFilter.filter(samples.i()); float[] q = mQBasebandFilter.filter(samples.q()); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java index ab269b92..6bdcb3eb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderHDQPSKInstrumented.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 @@ -58,8 +58,6 @@ public class P25P2DecoderHDQPSKInstrumented extends P25P2DecoderHDQPSK @Override public void receive(ComplexSamples samples) { - mMessageFramer.setCurrentTime(System.currentTimeMillis()); - float[] i = mIBasebandFilter.filter(samples.i()); float[] q = mQBasebandFilter.filter(samples.q()); 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 1baf0982..a7609abf 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 @@ -55,7 +55,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.MotorolaT 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.structure.AcknowledgeResponseFNEAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.AcknowledgeResponseFNEExtended; @@ -127,7 +126,6 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorol 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.Collections; @@ -191,7 +189,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie protected void resetState() { super.resetState(); - closeCurrentCallEvent(true, false); + mTrafficChannelManager.processP2TrafficCallEnd(getCurrentFrequency(), getTimeslot(), System.currentTimeMillis(), "RESET STATE INVOKED"); mEndPttOnFacchCounter = 0; } @@ -238,13 +236,16 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie { broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot())); } + + //If we're tracking the call event, update the duration on it + mTrafficChannelManager.processP2TrafficVoice(getCurrentFrequency(), getTimeslot(), message.getTimestamp()); } 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()); + mTrafficChannelManager.processP2TrafficCurrentUser(getCurrentFrequency(), getTimeslot(), ess.getEncryptionKey(), ess.getTimestamp()); if(ess.isEncrypted()) { @@ -257,11 +258,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie } else if(message instanceof MotorolaTalkerAliasComplete tac) { - //Debug -// if(!mTrafficChannelManager.getTalkerAliasManager().hasAlias(tac.getRadio())) -// { -// System.out.println(tac); -// } mTrafficChannelManager.getTalkerAliasManager().update(tac.getRadio(), tac.getAlias()); } } @@ -279,6 +275,11 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie case MAC_0_SIGNAL: continueState(State.CONTROL); break; + case MAC_6_HANGTIME: + //During hangtime, the from talker is no longer involved in the call ... remove it because sometimes + //we don't get the end PTT to signal the end. + getIdentifierCollection().remove(Role.FROM); + break; } MacStructure mac = message.getMacStructure(); @@ -302,9 +303,19 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie 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: + case TDMA_21_GROUP_VOICE_CHANNEL_USER_EXTENDED: + case TDMA_22_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: processChannelUser(message, mac); break; case TDMA_05_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_IMPLICIT: + case TDMA_25_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT: + case PHASE1_42_GROUP_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: + case PHASE1_46_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED: + case PHASE1_49_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: + case PHASE1_C3_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + case PHASE1_C6_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_VCH: + case PHASE1_C7_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED_LCCH: + case PHASE1_C9_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: processChannelGrantUpdate(message, mac); break; case TDMA_08_NULL_AVOID_ZERO_BIAS: @@ -317,13 +328,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie 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; @@ -337,33 +341,29 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie case PHASE1_PARTITION_1_UNKNOWN_OPCODE: break; case PHASE1_40_GROUP_VOICE_CHANNEL_GRANT_IMPLICIT: + case PHASE1_44_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_ABBREVIATED: + case PHASE1_48_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_IMPLICIT: + case PHASE1_54_SNDCP_DATA_CHANNEL_GRANT: + case PHASE1_C8_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_EXPLICIT: + case PHASE1_C0_GROUP_VOICE_CHANNEL_GRANT_EXPLICIT: + case PHASE1_C4_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_VCH: + case PHASE1_CF_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_LCCH: processChannelGrant(message, mac); break; case PHASE1_41_GROUP_VOICE_SERVICE_REQUEST: //Inbound only. break; - case PHASE1_42_GROUP_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: - processChannelGrantUpdate(message, mac); - break; - case PHASE1_44_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_ABBREVIATED: - processChannelGrant(message, mac); - break; case PHASE1_45_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED: - processAnswer(message, mac); - break; - case PHASE1_46_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED: - processChannelGrantUpdate(message, mac); - break; - case PHASE1_48_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_IMPLICIT: - processChannelGrant(message, mac); - break; - case PHASE1_49_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT: - processChannelGrantUpdate(message, mac); - break; case PHASE1_4A_TELEPHONE_INTERCONNECT_ANSWER_RESPONSE: + case PHASE1_C5_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED: processAnswer(message, mac); break; case PHASE1_4C_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED: + case PHASE1_5D_RADIO_UNIT_MONITOR_COMMAND_OBSOLETE: + case PHASE1_5E_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_ABBREVIATED: + case PHASE1_CC_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_VCH: + case PHASE1_CD_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_LCCH: + case PHASE1_DE_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_EXTENDED: processRadioUnitMonitor(message, mac); break; case PHASE1_52_SNDCP_DATA_CHANNEL_REQUEST: @@ -372,35 +372,37 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie case PHASE1_53_SNDCP_DATA_PAGE_RESPONSE: //Ignore - this is an inbound request by the SU break; - case PHASE1_54_SNDCP_DATA_CHANNEL_GRANT: - processChannelGrant(message, mac); - break; case PHASE1_55_SNDCP_DATA_PAGE_REQUEST: processDataPageRequest(message, mac); break; case PHASE1_58_STATUS_UPDATE_ABBREVIATED: - processStatus(message, mac); - break; case PHASE1_5A_STATUS_QUERY_ABBREVIATED: + 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_5C_MESSAGE_UPDATE_ABBREVIATED: + case PHASE1_CE_MESSAGE_UPDATE_EXTENDED_LCCH: + case PHASE1_DC_MESSAGE_UPDATE_EXTENDED_VCH: processMessageUpdate(message, mac); break; - case PHASE1_5D_RADIO_UNIT_MONITOR_COMMAND_OBSOLETE: - case PHASE1_5E_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_ABBREVIATED: - processRadioUnitMonitor(message, mac); - break; case PHASE1_5F_CALL_ALERT_ABBREVIATED: + case PHASE1_CB_CALL_ALERT_EXTENDED_LCCH: + case PHASE1_DF_CALL_ALERT_EXTENDED_VCH: processCallAlert(message, mac); break; case PHASE1_60_ACKNOWLEDGE_RESPONSE_FNE_ABBREVIATED: + case PHASE1_E0_ACKNOWLEDGE_RESPONSE_FNE_EXTENDED: processAcknowledge(message, mac); break; case PHASE1_61_QUEUED_RESPONSE: processQueued(message, mac); break; case PHASE1_64_EXTENDED_FUNCTION_COMMAND_ABBREVIATED: + case PHASE1_E4_EXTENDED_FUNCTION_COMMAND_EXTENDED_VCH: + case PHASE1_E5_EXTENDED_FUNCTION_COMMAND_EXTENDED_LCCH: processExtendedFunctionCommand(message, mac); break; case PHASE1_67_DENY_RESPONSE: @@ -408,6 +410,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie break; case PHASE1_68_GROUP_AFFILIATION_RESPONSE_ABBREVIATED: case PHASE1_6A_GROUP_AFFILIATION_QUERY_ABBREVIATED: + case PHASE1_E8_GROUP_AFFILIATION_RESPONSE_EXTENDED: + case PHASE1_EA_GROUP_AFFILIATION_QUERY_EXTENDED: processAffiliation(message, mac); break; case PHASE1_6B_LOCATION_REGISTRATION_RESPONSE: @@ -416,6 +420,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie case PHASE1_6C_UNIT_REGISTRATION_RESPONSE_ABBREVIATED: case PHASE1_6D_UNIT_REGISTRATION_COMMAND_ABBREVIATED: case PHASE1_6F_DEREGISTRATION_ACKNOWLEDGE: + case PHASE1_EC_UNIT_REGISTRATION_RESPONSE_EXTENDED: processUnitRegistration(message, mac); break; case PHASE1_70_SYNCHRONIZATION_BROADCAST: @@ -423,6 +428,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie break; case PHASE1_71_AUTHENTICATION_DEMAND: case PHASE1_72_AUTHENTICATION_FNE_RESPONSE_ABBREVIATED: + case PHASE1_F2_AUTHENTICATION_FNE_RESPONSE_EXTENDED: processAuthentication(message, mac); break; case PHASE1_76_ROAMING_ADDRESS_COMMAND: @@ -431,19 +437,24 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie 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: + case PHASE1_D6_SNDCP_DATA_CHANNEL_ANNOUNCEMENT: + case PHASE1_E9_SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + 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; + case PHASE1_75_TIME_AND_DATE_ANNOUNCEMENT: + //Ignore + break; /** * Partition 3 Opcodes @@ -458,88 +469,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie } 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_C9_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: - processChannelGrantUpdate(message, mac); - break; - case PHASE1_CB_CALL_ALERT_EXTENDED_LCCH: - processCallAlert(message, mac); - break; - case PHASE1_CC_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_VCH: - case PHASE1_CD_RADIO_UNIT_MONITOR_COMMAND_EXTENDED_LCCH: - processRadioUnitMonitor(message, mac); - break; - case PHASE1_CE_MESSAGE_UPDATE_EXTENDED_LCCH: - processMessageUpdate(message, mac); - break; - case PHASE1_CF_UNIT_TO_UNIT_VOICE_SERVICE_CHANNEL_GRANT_EXTENDED_LCCH: - processChannelGrant(message, mac); - break; - case PHASE1_D6_SNDCP_DATA_CHANNEL_ANNOUNCEMENT: - processNetwork(message, mac); - break; - 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_DC_MESSAGE_UPDATE_EXTENDED_VCH: - processMessageUpdate(message, mac); - break; - case PHASE1_DE_RADIO_UNIT_MONITOR_ENHANCED_COMMAND_EXTENDED: - processRadioUnitMonitor(message, mac); - break; - 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_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 @@ -835,7 +764,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie //Add the traffic channel to the IC ic.update(cgdp.getChannel()); mTrafficChannelManager.processP2ChannelGrant(cgdp.getChannel(), cgdp.getServiceOptions(), ic, mac.getOpcode(), - message.getTimestamp()); + message.getTimestamp(), mac.toString()); } } } @@ -864,7 +793,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getGroupAddress1(), message.getTimestamp()); mic.update(cgu.getChannel1()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions1(), mic, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } if(cgu.hasGroup2()) @@ -876,7 +805,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic2 = getIdentifierCollectionForUser(cgu.getGroupAddress2(), message.getTimestamp()); mic2.update(cgu.getChannel1()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions2(), mic2, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } } @@ -889,7 +818,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic3 = getIdentifierCollectionForUser(cgu.getGroupAddress3(), message.getTimestamp()); mic3.update(cgu.getChannel1()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions3(), mic3, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } } } @@ -904,7 +833,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getGroupAddress1(), message.getTimestamp()); mic.update(cgu.getChannel1()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions1(), mic, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } if(cgu.hasGroup2()) @@ -916,7 +845,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic2 = getIdentifierCollectionForUser(cgu.getGroupAddress2(), message.getTimestamp()); mic2.update(cgu.getChannel1()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), cgu.getServiceOptions2(), mic2, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } } } @@ -934,7 +863,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getGroupAddress1(), message.getTimestamp()); mic.update(cgu.getChannel1()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), serviceOptions, mic, mac.getOpcode(), - message.getTimestamp()); + message.getTimestamp(), mac.toString()); } if(cgu.hasGroup2()) @@ -946,7 +875,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic2 = getIdentifierCollectionForUser(cgu.getGroupAddress2(), message.getTimestamp()); mic2.update(cgu.getChannel1()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel1(), serviceOptions, mic2, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } } } @@ -963,7 +892,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); mic.update(cgu.getChannel()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), serviceOptions, mic, mac.getOpcode(), - message.getTimestamp()); + message.getTimestamp(), mac.toString()); } } break; @@ -979,7 +908,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); mic.update(cgu.getChannel()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), serviceOptions, mic, mac.getOpcode(), - message.getTimestamp()); + message.getTimestamp(), mac.toString()); } } break; @@ -993,7 +922,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getGroupAddress(), message.getTimestamp()); mic.update(cgu.getChannel()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), cgu.getServiceOptions(), mic, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } } break; @@ -1009,7 +938,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); mic.update(cgu.getChannel()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), serviceOptions, mic, mac.getOpcode(), - message.getTimestamp()); + message.getTimestamp(), mac.toString()); } } break; @@ -1023,7 +952,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); mic.update(cgu.getChannel()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), cgu.getServiceOptions(), mic, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } } break; @@ -1037,7 +966,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUsers(cgu.getIdentifiers(), message.getTimestamp()); mic.update(cgu.getChannel()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), cgu.getServiceOptions(), mic, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } } break; @@ -1052,7 +981,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getPatchgroup(), message.getTimestamp()); mic.update(cgu.getChannel()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannel(), cgu.getServiceOptions(), mic, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } } break; @@ -1070,7 +999,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic = getIdentifierCollectionForUser(cgu.getPatchgroupA(), message.getTimestamp()); mic.update(cgu.getChannelA()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannelA(), serviceOptions, mic, mac.getOpcode(), - message.getTimestamp()); + message.getTimestamp(), mac.toString()); } if(cgu.hasPatchgroupB()) @@ -1083,7 +1012,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie MutableIdentifierCollection mic2 = getIdentifierCollectionForUser(cgu.getPatchgroupB(), message.getTimestamp()); mic2.update(cgu.getChannelB()); mTrafficChannelManager.processP2ChannelUpdate(cgu.getChannelB(), serviceOptions, mic2, - mac.getOpcode(), message.getTimestamp()); + mac.getOpcode(), message.getTimestamp(), mac.toString()); } } } @@ -1129,6 +1058,22 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie } } + @Override + public void setCurrentChannel(IChannelDescriptor channel) + { + if(channel == null) + { + return; + } + + if(channel instanceof APCO25Channel apco25Channel && apco25Channel.getTimeslot() != getTimeslot()) + { + channel = apco25Channel.decorateAs(getTimeslot()); + } + + super.setCurrentChannel(channel); + } + /** * Channel user (ie current user on this channel). * @@ -1152,7 +1097,9 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie { if(mac instanceof IServiceOptionsProvider sop) { - IChannelDescriptor currentChannel = mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), getCurrentChannel(), sop.getServiceOptions(), mac.getOpcode(), getIdentifierCollection().copyOf(), message.getTimestamp(), null); + IChannelDescriptor currentChannel = mTrafficChannelManager.processP2TrafficCurrentUser(getCurrentFrequency(), + getTimeslot(), getCurrentChannel(), sop.getServiceOptions(), mac.getOpcode(), + getIdentifierCollection().copyOf(), message.getTimestamp(), null, message.toString()); if(getCurrentChannel() == null) { @@ -1195,9 +1142,14 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof PushToTalk ptt) { VoiceServiceOptions vso = ptt.isEncrypted() ? VoiceServiceOptions.createEncrypted() : VoiceServiceOptions.createUnencrypted(); - mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), getCurrentChannel(), vso, + + //First TCM call creates the tracked event and second call starts the call and updates the duration + mTrafficChannelManager.processP2TrafficCurrentUser(getCurrentFrequency(), getTimeslot(), getCurrentChannel(), vso, mac.getOpcode(), getIdentifierCollection().copyOf(), message.getTimestamp(), - ptt.isEncrypted() ? ptt.getEncryptionKey().toString() : null); + ptt.isEncrypted() ? ptt.getEncryptionKey().toString() : null, message.toString()); + + mTrafficChannelManager.processP2TrafficVoice(getCurrentFrequency(), getTimeslot(), message.getTimestamp()); + broadcast(new DecoderStateEvent(this, Event.START, ptt.isEncrypted() ? State.ENCRYPTED : State.CALL, getTimeslot())); } } @@ -1256,7 +1208,16 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie { if(mac instanceof EndPushToTalk) { - closeCurrentCallEvent(true, false); + //No matter what, remove the FROM identifier on end PTT. + getIdentifierCollection().remove(Role.FROM); + + //Only reset the identifiers if the call event is closed out, otherwise we might have a timing issue + //between the control channel and the traffic channel. + if(mTrafficChannelManager.processP2TrafficEndPushToTalk(getCurrentFrequency(), getTimeslot(), + message.getTimestamp(), "END PUSH TO TALK - " + message)) + { + getIdentifierCollection().remove(IdentifierClass.USER); + } if(message.getDataUnitID().isFACCH()) { @@ -1269,8 +1230,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie return; //don't issue a reset state after this. } } - - continueState(State.RESET); } } @@ -1514,7 +1473,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie .build(); broadcast(decodeEvent); mTrafficChannelManager.broadcast(decodeEvent); - mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), gps.getLocation(), + mTrafficChannelManager.processP2TrafficCurrentUser(getCurrentFrequency(), getTimeslot(), gps.getLocation(), message.getTimestamp()); } } @@ -1543,7 +1502,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie { if(mac instanceof MacRelease mr) { - closeCurrentCallEvent(true, false); + mTrafficChannelManager.processP2TrafficCallEnd(getCurrentFrequency(), getTimeslot(), message.getTimestamp(), "MAC RELEASE: " + mac.toString()); + getIdentifierCollection().remove(IdentifierClass.USER); broadcast(message, mac, DecodeEventType.COMMAND, (mr.isForcedPreemption() ? "FORCED " : "") + "CALL PREEMPTION" + (mr.isTalkerPreemption() ? " BY USER" : " BY CONTROLLER")); @@ -1746,7 +1706,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie { P25TalkerAliasIdentifier alias = talkerAlias.getAlias(); getIdentifierCollection().update(alias); - mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), alias, message.getTimestamp()); + mTrafficChannelManager.processP2TrafficCurrentUser(getCurrentFrequency(), getTimeslot(), alias, message.getTimestamp()); //Add the alias to the talker alias manager if we know the associated radio Identifier from = getIdentifierCollection().getFromIdentifier(); @@ -1839,50 +1799,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie } - /** - * Updates or creates a current call event. - * - * @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(MacOpcode macOpcode, ServiceOptions serviceOptions, String details, long timestamp) - { - 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())); - } - - } - - /** - * Ends/closes the current call event. - * - * @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(boolean resetIdentifiers, boolean isIdleNull) - { - mTrafficChannelManager.closeP2CallEvent(getCurrentFrequency(), getTimeslot(), isIdleNull); - - if(resetIdentifiers) - { - getIdentifierCollection().remove(IdentifierClass.USER); - } - else - { - //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); - } - } - /** * Indicates if the current set of identifiers contains an encryption key indicating that the communication is * encrypted. 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 2c6f0028..8812f52a 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 @@ -20,7 +20,6 @@ package io.github.dsheirer.module.decode.p25.phase2; import io.github.dsheirer.alias.AliasList; import io.github.dsheirer.alias.AliasModel; -import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop; import io.github.dsheirer.dsp.symbol.Dibit; @@ -30,11 +29,8 @@ 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; @@ -59,23 +55,15 @@ import org.slf4j.LoggerFactory; public class P25P2MessageFramer implements Listener { private final static Logger mLog = LoggerFactory.getLogger(P25P2MessageFramer.class); - private P25P2SuperFrameDetector mSuperFrameDetector; - private boolean mAssemblingMessage = false; - private CorrectedBinaryMessage mBinaryMessage; - private DataUnitID mDataUnitID; - private PDUSequence mPDUSequence; - private int[] mCorrectedNID; - private int mNAC; - private int mStatusSymbolDibitCounter = 0; - private int mTrailingDibitsToSuppress = 0; - private double mBitRate; - private long mCurrentTime = System.currentTimeMillis(); - public P25P2MessageFramer(IPhaseLockedLoop phaseLockedLoop, int bitRate) + /** + * Constructs an instance + * @param phaseLockedLoop for use with the super frame detector + */ + public P25P2MessageFramer(IPhaseLockedLoop phaseLockedLoop) { mSuperFrameDetector = new P25P2SuperFrameDetector(phaseLockedLoop); - mBitRate = bitRate; } /** @@ -109,41 +97,6 @@ public class P25P2MessageFramer implements Listener mSuperFrameDetector.setSyncDetectListener(listener); } - /** - * Current timestamp or timestamp of incoming message buffers that is continuously updated to as - * close as possible to the bits processed for the expected baud rate. - * - * @return - */ - private long getTimestamp() - { - return mCurrentTime; - } - - /** - * Sets the current time. This should be invoked by an incoming message buffer stream. - * - * @param currentTime - */ - public void setCurrentTime(long currentTime) - { - mCurrentTime = currentTime; - } - - /** - * Updates the current timestamp based on the number of bits processed versus the bit rate per second - * in order to keep an accurate running timestamp to use for timestamped message creation. - * - * @param bitsProcessed thus far - */ - private void updateBitsProcessed(int bitsProcessed) - { - if(bitsProcessed > 0) - { - mCurrentTime += (long)((double)bitsProcessed / mBitRate * 1000.0); - } - } - /** * Registers the listener for messages produced by this message framer * @@ -154,11 +107,6 @@ public class P25P2MessageFramer implements Listener mSuperFrameDetector.setListener(messageListener); } - public P25P2SuperFrameDetector getSuperFrameDetector() - { - return mSuperFrameDetector; - } - /** * Primary method for streaming decoded symbol dibits for message framing. * @@ -170,18 +118,6 @@ public class P25P2MessageFramer implements Listener mSuperFrameDetector.receive(dibit); } - private void reset(int bitsProcessed) - { - updateBitsProcessed(bitsProcessed); - mPDUSequence = null; - mBinaryMessage = null; - mAssemblingMessage = false; - mDataUnitID = null; - mNAC = 0; - mSuperFrameDetector.reset(); - mStatusSymbolDibitCounter = 0; - } - /** * Primary method for streaming decoded symbol byte arrays. * @@ -266,7 +202,7 @@ public class P25P2MessageFramer implements Listener processingChain.start(); - P25P2MessageFramer messageFramer = new P25P2MessageFramer(null, DecoderType.P25_PHASE2.getProtocol().getBitRate()); + P25P2MessageFramer messageFramer = new P25P2MessageFramer(null); messageFramer.setScrambleParameters(scrambleParameters); P25P2MessageProcessor messageProcessor = new P25P2MessageProcessor(); messageFramer.setListener(messageProcessor); 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 6a98aec3..a24faff4 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 @@ -25,7 +25,7 @@ package io.github.dsheirer.module.decode.p25.reference; public abstract class ServiceOptions { protected static final int EMERGENCY_FLAG = 0x80; - protected static final int ENCRYPTION_FLAG = 0x40; + public static final int ENCRYPTION_FLAG = 0x40; protected static final int DUPLEX = 0x20; protected static final int SESSION_MODE = 0x10; protected int mServiceOptions;