diff --git a/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java b/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java
index b2814c7b4..5df660126 100644
--- a/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java
+++ b/src/main/java/io/github/dsheirer/identifier/MutableIdentifierCollection.java
@@ -1,35 +1,33 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
- * * ******************************************************************************
- * * Copyright (C) 2014-2019 Dennis Sheirer
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU General Public License as published by
- * * the Free Software Foundation, either version 3 of the License, or
- * * (at your option) any later version.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU General Public License for more details.
- * *
- * * You should have received a copy of the GNU General Public License
- * * along with this program. If not, see
- * * *****************************************************************************
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
*/
package io.github.dsheirer.identifier;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
+import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier;
+import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.sample.Listener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Identifier collection with methods for changing or updating managed identifiers
@@ -209,6 +207,15 @@ public void update(Identifier identifier)
remove(existing);
add(identifier);
}
+ //Always replace a radio identifier with a fully qualified variant of itself
+ else if(existing instanceof RadioIdentifier &&
+ !(existing instanceof FullyQualifiedRadioIdentifier) &&
+ identifier instanceof FullyQualifiedRadioIdentifier &&
+ existing.getValue().equals(identifier.getValue()))
+ {
+ remove(existing);
+ add(identifier);
+ }
}
else
{
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 905133b09..94a4b6106 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
@@ -31,8 +31,10 @@
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.alias.TalkerAliasManager;
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
+import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroupPreLoadDataContent;
import io.github.dsheirer.identifier.scramble.ScrambleParameterIdentifier;
+import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.log.LoggingSuppressor;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageListener;
@@ -709,6 +711,78 @@ public void processP1ControlDirectedChannelGrant(APCO25Channel apco25Channel, Se
}
}
+ /**
+ * Process a Phase 1 HDU indicating a call start.
+ *
+ * @param frequency for the call event
+ * @param talkgroup to update within the event.
+ * @param eki for encryption settings.
+ * @param timestamp for the update
+ */
+ public void processP1TrafficCallStart(long frequency, Identifier> talkgroup, Identifier> radio,
+ EncryptionKeyIdentifier eki, ServiceOptions serviceOptions,
+ IChannelDescriptor channelDescriptor, long timestamp)
+ {
+ mLock.lock();
+
+ try
+ {
+ P25TrafficChannelEventTracker tracker = getTracker(frequency, P25P1Message.TIMESLOT_1);
+
+ if(tracker != null)
+ {
+ removeTracker(frequency, P25P1Message.TIMESLOT_1);
+ }
+
+ DecodeEventType decodeEventType = getDecodeEventType(talkgroup, eki);
+
+ MutableIdentifierCollection mic = new MutableIdentifierCollection();
+ mic.update(talkgroup);
+ mic.update(radio);
+ mic.update(eki);
+
+ //Create a new event for the current call.
+ P25ChannelGrantEvent callEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions)
+ .channelDescriptor(channelDescriptor)
+ .details("PHASE 1 CALL " + (serviceOptions != null ? serviceOptions : ""))
+ .identifiers(mic)
+ .build();
+
+ tracker = new P25TrafficChannelEventTracker(callEvent);
+ addTracker(tracker, frequency, P25P1Message.TIMESLOT_1);
+ broadcast(tracker);
+ }
+ finally
+ {
+ mLock.unlock();
+ }
+ }
+
+ /**
+ * Determines the HDU message call decode event type from the talkgroup identifier and encryption key
+ * @param talkgroup to inspect
+ * @param eki to inspect
+ * @return decode event type.
+ */
+ private static DecodeEventType getDecodeEventType(Identifier talkgroup, EncryptionKeyIdentifier eki)
+ {
+ DecodeEventType decodeEventType = null;
+
+ if(talkgroup instanceof PatchGroupIdentifier)
+ {
+ decodeEventType = eki.isEncrypted() ? DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED : DecodeEventType.CALL_PATCH_GROUP;
+ }
+ else if(talkgroup instanceof TalkgroupIdentifier ti && ti.getValue() == 0) //Unit-to-Unit private call
+ {
+ decodeEventType = eki.isEncrypted() ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT;
+ }
+ else
+ {
+ decodeEventType = eki.isEncrypted() ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
+ }
+ return decodeEventType;
+ }
+
/**
* Updates an identifier for an ongoing call event on the frequency and updates the event duration timestamp.
*
@@ -755,6 +829,55 @@ public void processP1TrafficCurrentUser(long frequency, Identifier identifier, l
}
}
+ /**
+ * Updates all current identifiers 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, 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 processP1TrafficCurrentUserIdentifiers(long frequency, List identifiers, long timestamp, String context)
+ {
+ mLock.lock();
+
+ try
+ {
+ P25TrafficChannelEventTracker tracker = getTracker(frequency, P25P1Message.TIMESLOT_1);
+
+ if(tracker != null && tracker.isComplete())
+ {
+ removeTracker(frequency, P25P1Message.TIMESLOT_1);
+ tracker = null;
+ }
+
+ if(tracker != null)
+ {
+ for(Identifier identifier : identifiers)
+ {
+ tracker.addIdentifierIfMissing(identifier);
+
+ //Add the encryption key to the call event details.
+ if(identifier instanceof EncryptionKeyIdentifier eki && eki.isEncrypted())
+ {
+ tracker.addDetailsIfMissing(eki.toString());
+ }
+ }
+
+ tracker.updateDurationTraffic(timestamp);
+ broadcast(tracker);
+ }
+ }
+ finally
+ {
+ mLock.unlock();
+ }
+ }
+
/**
* Processes traffic channel announced current user information.
* @param frequency of the traffic channel
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1CallSequenceRecorder.java b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1CallSequenceRecorder.java
index 1524822ab..0e4c029da 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1CallSequenceRecorder.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1CallSequenceRecorder.java
@@ -39,7 +39,7 @@
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU1Message;
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU2Message;
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDUMessage;
-import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
+import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULCMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDUMessage;
import io.github.dsheirer.preference.UserPreferences;
import java.util.List;
@@ -119,9 +119,9 @@ public void process(P25P1Message message)
{
process((LDUMessage)message);
}
- else if(message instanceof TDULinkControlMessage)
+ else if(message instanceof TDULCMessage)
{
- process((TDULinkControlMessage)message);
+ process((TDULCMessage)message);
}
else if(message instanceof TDUMessage)
{
@@ -133,7 +133,7 @@ else if (message instanceof HDUMessage)
}
}
- private void process(TDULinkControlMessage tdulc)
+ private void process(TDULCMessage tdulc)
{
process(tdulc.getLinkControlWord());
}
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 20fb940d1..a96cac45e 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
@@ -61,7 +61,6 @@
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData;
-import io.github.dsheirer.module.decode.p25.phase1.message.lc.IExtendedSourceMessage;
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;
@@ -113,7 +112,7 @@
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.response.ResponseMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.isp.UMBTCTelephoneInterconnectRequestExplicitDialing;
-import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
+import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULCMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.L3HarrisGroupRegroupExplicitEncryptionCommand;
@@ -190,6 +189,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
private final P25P1NetworkConfigurationMonitor mNetworkConfigurationMonitor;
private final Listener mChannelEventListener;
private P25TrafficChannelManager mTrafficChannelManager;
+ private ServiceOptions mCurrentServiceOptions;
/**
* Constructs an APCO-25 decoder state with an optional traffic channel manager.
@@ -316,10 +316,6 @@ public void receive(IMessage iMessage)
break;
}
}
- else if(iMessage instanceof IExtendedSourceMessage esm && iMessage instanceof LinkControlWord lcw)
- {
- processLC(lcw, esm.getTimestamp(), esm.isTerminator());
- }
else if(iMessage instanceof MotorolaTalkerAliasComplete tac)
{
mTrafficChannelManager.getTalkerAliasManager().update(tac.getRadio(), tac.getAlias());
@@ -413,7 +409,6 @@ private void processLCChannelUser(LinkControlWord lcw, long timestamp)
getIdentifierCollection().update(updated);
DecodeEventType decodeEventType = getLCDecodeEventType(lcw);
-
ServiceOptions serviceOptions = null;
if(lcw instanceof IServiceOptionsProvider sop)
@@ -831,17 +826,31 @@ private void processHDU(IMessage message)
if(message.isValid() && message instanceof HDUMessage hdu)
{
HeaderData headerData = hdu.getHeaderData();
- //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())
- {
- broadcast(new DecoderStateEvent(this, Event.START, State.ENCRYPTED));
- }
- else
+ if(headerData.isValid())
{
- broadcast(new DecoderStateEvent(this, Event.START, State.CALL));
+ Identifier talkgroup = headerData.getTalkgroup();
+
+ //Run the talkgroup through the patch group manager so we don't get a plain talkgroup in addition to the patch
+ //Talkgroup value of zero indicates a unit-to-unit call, so don't attempt to update it as a patch group
+ if(headerData.getTalkgroup().getValue() > 0)
+ {
+ talkgroup = mPatchGroupManager.update(talkgroup, message.getTimestamp());
+ }
+
+ Identifier> radio = getIdentifierCollection().getFromIdentifier();
+
+ mTrafficChannelManager.processP1TrafficCallStart(getCurrentFrequency(), talkgroup, radio,
+ headerData.getEncryptionKey(), mCurrentServiceOptions, getCurrentChannel(), message.getTimestamp());
+
+ if(headerData.isEncryptedAudio())
+ {
+ broadcast(new DecoderStateEvent(this, Event.START, State.ENCRYPTED));
+ }
+ else
+ {
+ broadcast(new DecoderStateEvent(this, Event.START, State.CALL));
+ }
}
}
}
@@ -862,6 +871,8 @@ private void processLDU(P25P1Message message)
if(lcw != null && lcw.isValid())
{
processLC(lcw, message.getTimestamp(), false);
+ mTrafficChannelManager.processP1TrafficCurrentUserIdentifiers(getCurrentFrequency(),
+ getIdentifierCollection().getIdentifiers(), message.getTimestamp(), ldu1.toString());
}
}
else if(message instanceof LDU2Message ldu2)
@@ -917,7 +928,7 @@ private void processTDULC(P25P1Message message)
getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM);
}
- if(message instanceof TDULinkControlMessage tdulc)
+ if(message instanceof TDULCMessage tdulc)
{
LinkControlWord lcw = tdulc.getLinkControlWord();
@@ -1831,7 +1842,10 @@ private void processLC(LinkControlWord lcw, long timestamp, boolean isTerminator
{
switch(lcw.getOpcode())
{
- //Calls in-progress on this channel
+ case SOURCE_ID_EXTENSION:
+ //Ignore - handled elsewhere
+ break;
+ //Calls getting ready to start or in-progress on this channel
case GROUP_VOICE_CHANNEL_USER:
case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER:
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER:
@@ -1839,7 +1853,9 @@ private void processLC(LinkControlWord lcw, long timestamp, boolean isTerminator
case UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED:
if(isTerminator)
{
- closeCurrentCallEvent(timestamp, lcw.toString());
+ mTrafficChannelManager.processP1TrafficCallEnd(getCurrentFrequency(), timestamp, lcw.toString());
+ List updated = mPatchGroupManager.update(lcw.getIdentifiers(), timestamp);
+ getIdentifierCollection().update(updated);
}
else
{
@@ -1941,11 +1957,6 @@ private void processLC(LinkControlWord lcw, long timestamp, boolean isTerminator
getIdentifierCollection().update(frequencyID);
}
- if(isTerminator)
- {
- closeCurrentCallEvent(timestamp, lcw.toString());
- }
-
mNetworkConfigurationMonitor.process(lcw);
break;
case NETWORK_STATUS_BROADCAST_EXPLICIT:
@@ -2162,9 +2173,6 @@ private void processLC(LinkControlWord lcw, long timestamp, boolean isTerminator
closeCurrentCallEvent(timestamp, lcw.toString());
}
break;
- case SOURCE_ID_EXTENSION:
- //Ignore - handled elsewhere
- break;
default:
if(isTerminator)
{
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java
index 9ee36a87d..882398fca 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java
@@ -19,10 +19,12 @@
package io.github.dsheirer.module.decode.p25.phase1;
import io.github.dsheirer.channel.IChannelDescriptor;
+import io.github.dsheirer.message.AbstractMessage;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver;
+import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.IExtendedSourceMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.HarrisTalkerAliasAssembler;
@@ -30,7 +32,8 @@
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkerAliasAssembler;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension;
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU1Message;
-import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
+import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU2Message;
+import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULCMessage;
import io.github.dsheirer.sample.Listener;
import java.util.List;
import java.util.Map;
@@ -59,9 +62,12 @@ public class P25P1MessageProcessor implements Listener
private Map mFrequencyBandMap = new TreeMap();
/**
- * Temporary holding of an extended source link control message while it awaits the extension message to arrive.
+ * Temporary holding for an extended source link control message while it awaits the extension message to arrive.
*/
- private IExtendedSourceMessage mExtendedSourceMessage;
+ private TDULCMessage mHeldTDULCMessage;
+ private HDUMessage mHeldHDUMessage;
+ private LDU1Message mHeldLDU1Message;
+ private LDU2Message mHeldLDU2Message;
/**
* Motorola talker alias assembler for link control header and data blocks.
@@ -103,120 +109,200 @@ public void preload(P25FrequencyBandPreloadDataContent content)
@Override
public void receive(IMessage message)
{
- //Optional message created during processing that should be sent after the current argument is sent.
- IMessage additionalMessageToSend = null;
-
if(message.isValid())
{
//Reassemble extended source link control messages.
- if(message instanceof LDU1Message ldu)
+ if(message instanceof LDU1Message ldu1)
{
- if(ldu.getLinkControlWord() instanceof IExtendedSourceMessage extendedSourceMessage)
+ LinkControlWord lcw = ldu1.getLinkControlWord();;
+
+ if(lcw instanceof IExtendedSourceMessage esm)
{
- additionalMessageToSend = reassembleLC(extendedSourceMessage);
+ if(lcw instanceof LCSourceIDExtension extension)
+ {
+ processSourceIDExtension(extension);
+ }
+ else if(esm.isExtensionRequired())
+ {
+ processSourceIDExtension(null);
+ mHeldLDU1Message = ldu1;
+ return;
+ }
}
- else if(ldu.getLinkControlWord() instanceof LCHarrisTalkerAliasBase harrisTalkerAlias)
+ else if(lcw instanceof LCHarrisTalkerAliasBase harrisTalkerAlias)
{
//Send the LCW to the harris talker alias assembler
- additionalMessageToSend = mHarrisTalkerAliasAssembler.process(harrisTalkerAlias, ldu.getTimestamp());
+ dispatch(mHarrisTalkerAliasAssembler.process(harrisTalkerAlias, ldu1.getTimestamp()));
}
+
+ //Flush any held messages
+ processSourceIDExtension(null);
+
+ dispatch(ldu1);
}
- else if(message instanceof TDULinkControlMessage tdu)
+ else if(message instanceof LDU2Message ldu2)
{
- LinkControlWord lcw = tdu.getLinkControlWord();
-
- if(lcw instanceof IExtendedSourceMessage extendedSourceMessage)
+ //If we held onto an LDU1 awaiting a source ID extension, then also hold onto this LDU2 and flush them in sequence.
+ if(mHeldLDU1Message != null)
{
- additionalMessageToSend = reassembleLC(extendedSourceMessage);
+ mHeldLDU2Message = ldu2;
}
- //Motorola carries the talker alias in the TDULC
- else if(mMotorolaTalkerAliasAssembler.add(lcw, message.getTimestamp()))
+ else
{
- additionalMessageToSend = mMotorolaTalkerAliasAssembler.assemble();
+ dispatch(ldu2);
}
-
- //Harris carries the talker alias in the LDU voice messages, so reset the assembler on TDULC.
- mHarrisTalkerAliasAssembler.reset();
}
-
- //Insert frequency band identifier update messages into channel-type messages */
- if(message instanceof IFrequencyBandReceiver)
+ else if(message instanceof TDULCMessage tdulc)
{
- IFrequencyBandReceiver receiver = (IFrequencyBandReceiver)message;
-
- List channels = receiver.getChannels();
+ LinkControlWord lcw = tdulc.getLinkControlWord();
- for(IChannelDescriptor channel : channels)
+ if(lcw instanceof IExtendedSourceMessage esm)
{
- int[] frequencyBandIdentifiers = channel.getFrequencyBandIdentifiers();
-
- for(int id : frequencyBandIdentifiers)
+ if(lcw instanceof LCSourceIDExtension sourceIDExtension)
+ {
+ processSourceIDExtension(sourceIDExtension);
+ }
+ else if(esm.isExtensionRequired())
{
- if(mFrequencyBandMap.containsKey(id))
- {
- channel.setFrequencyBand(mFrequencyBandMap.get(id));
- }
+ processSourceIDExtension(null);
+ mHeldTDULCMessage = tdulc;
+ return;
}
}
- }
- //Store band identifiers so that they can be injected into channel type messages
- if(message instanceof IFrequencyBand)
- {
- IFrequencyBand bandIdentifier = (IFrequencyBand)message;
-
- //Only store the frequency band if it's new so we don't hold on to more than one instance of the
- //frequency band message. Otherwise, we'll hold on to several instances of each message as they get
- //injected into other messages with channel information.
- if(!mFrequencyBandMap.containsKey(bandIdentifier.getIdentifier()))
+ //Motorola carries the talker alias in the TDULC
+ else if(mMotorolaTalkerAliasAssembler.add(lcw, message.getTimestamp()))
{
- mFrequencyBandMap.put(bandIdentifier.getIdentifier(), bandIdentifier);
+ dispatch(mMotorolaTalkerAliasAssembler.assemble());
}
- }
- }
- if(mMessageListener != null)
- {
- if(message != null)
+ //Harris carries the talker alias in the LDU1 link control messages, so reset the assembler on TDULC.
+ mHarrisTalkerAliasAssembler.reset();
+
+ dispatch(tdulc);
+ }
+ else if(message instanceof HDUMessage hdu && mHeldTDULCMessage != null)
{
- mMessageListener.receive(message);
+ //If the last TDULC message was held because it needs an extension that can arrive in the first LDU1,
+ //hold onto the intermediate HDU message and send everything (TDU, HDU, LDU) in correct sequence.
+ mHeldHDUMessage = hdu;
}
-
- if(additionalMessageToSend != null)
+ else
{
- mMessageListener.receive(additionalMessageToSend);
+ //flush any held messages
+ processSourceIDExtension(null);
+ dispatch(message);
}
}
}
/**
- * Processes link control words to reassemble source ID extension messages.
- * @param lcw to process
- * @return reassembled link control or null if the original lcw is a receiver (which we hold onto and send later)
+ * Processes the source extension message and attaches it to any held TDULC or LDU1 with LC messages and flushes any
+ * held messages.
+ * @param extension to attach to held message(s) or null extension to flush all held messages.
+ */
+ private void processSourceIDExtension(LCSourceIDExtension extension)
+ {
+ if(mHeldTDULCMessage != null && mHeldTDULCMessage.getLinkControlWord() instanceof IExtendedSourceMessage esm)
+ {
+ esm.setSourceIDExtension(extension);
+ dispatch(mHeldTDULCMessage);
+ mHeldTDULCMessage = null;
+ dispatch(mHeldHDUMessage);
+ mHeldHDUMessage = null;
+ }
+
+ if(mHeldLDU1Message != null && mHeldLDU1Message.getLinkControlWord() instanceof IExtendedSourceMessage esm)
+ {
+ esm.setSourceIDExtension(extension);
+ dispatch(mHeldLDU1Message);
+ mHeldLDU1Message = null;
+ dispatch(mHeldLDU2Message);
+ mHeldLDU2Message = null;
+ }
+ }
+
+ /**
+ * Post-process the message for frequency band details.
+ * @param message to post process and dispatch
*/
- private IMessage reassembleLC(IExtendedSourceMessage lcw)
+ private void dispatch(IMessage message)
{
- IMessage toReturn = null;
+ if(message == null)
+ {
+ return;
+ }
+
+ processForFrequencyBands(message);
- if(lcw instanceof IExtendedSourceMessage extendedSourceMessage && extendedSourceMessage.isExtensionRequired())
+ //Also process the link control messages for frequency bands.
+ if(message instanceof LDU1Message ldu1)
{
- mExtendedSourceMessage = extendedSourceMessage;
+ processForFrequencyBands(ldu1.getLinkControlWord());
}
- else if(lcw instanceof LCSourceIDExtension sie && mExtendedSourceMessage != null)
+ else if(message instanceof TDULCMessage tdulc)
{
- mExtendedSourceMessage.setSourceIDExtension(sie);
- toReturn = mExtendedSourceMessage;
- mExtendedSourceMessage = null;
+ processForFrequencyBands(tdulc.getLinkControlWord());
}
- else
+
+ if(mMessageListener != null)
{
- //The source extension message should always immediately follow the message that is being extended, so if
- //we get a message that is not an extended message or the extension, then we've missed the extension and we
- //should nullify any extended message that's waiting for the extension.
- mExtendedSourceMessage = null;
+ mMessageListener.receive(message);
}
+ }
- return toReturn;
+ /**
+ * Captures IDEN_UPDATE messages and attaches them to messages with channel descriptors.
+ * @param message to process for frequency band assembly.
+ */
+ private void processForFrequencyBands(IMessage message)
+ {
+ if(message instanceof AbstractMessage am)
+ {
+ processForFrequencyBands(am);
+ }
+ }
+
+ /**
+ * Captures IDEN_UPDATE messages and attaches them to messages with channel descriptors.
+ * @param message to process for frequency band assembly.
+ */
+ private void processForFrequencyBands(AbstractMessage message)
+ {
+ //Insert frequency band identifier update messages into channel-type messages */
+ if(message instanceof IFrequencyBandReceiver)
+ {
+ IFrequencyBandReceiver receiver = (IFrequencyBandReceiver)message;
+
+ List channels = receiver.getChannels();
+
+ for(IChannelDescriptor channel : channels)
+ {
+ int[] frequencyBandIdentifiers = channel.getFrequencyBandIdentifiers();
+
+ for(int id : frequencyBandIdentifiers)
+ {
+ if(mFrequencyBandMap.containsKey(id))
+ {
+ channel.setFrequencyBand(mFrequencyBandMap.get(id));
+ }
+ }
+ }
+ }
+
+ //Store band identifiers so that they can be injected into channel type messages
+ if(message instanceof IFrequencyBand)
+ {
+ IFrequencyBand bandIdentifier = (IFrequencyBand)message;
+
+ //Only store the frequency band if it's new so we don't hold on to more than one instance of the
+ //frequency band message. Otherwise, we'll hold on to several instances of each message as they get
+ //injected into other messages with channel information.
+ if(!mFrequencyBandMap.containsKey(bandIdentifier.getIdentifier()))
+ {
+ mFrequencyBandMap.put(bandIdentifier.getIdentifier(), bandIdentifier);
+ }
+ }
}
/**
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25MessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25MessageFactory.java
index fe61a8fb4..b7eaf7789 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25MessageFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/P25MessageFactory.java
@@ -23,7 +23,7 @@
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU1Message;
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU2Message;
-import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
+import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULCMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDUMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.vselp.VSELP1Message;
import io.github.dsheirer.module.decode.p25.phase1.message.vselp.VSELP2Message;
@@ -65,7 +65,7 @@ public static P25P1Message create(P25P1DataUnitID dataUnitID, int nac, long time
case TERMINATOR_DATA_UNIT:
return new TDUMessage(message, nac, timestamp);
case TERMINATOR_DATA_UNIT_LINK_CONTROL:
- return new TDULinkControlMessage(message, nac, timestamp);
+ return new TDULCMessage(message, nac, timestamp);
case TRUNKING_SIGNALING_BLOCK_1:
case TRUNKING_SIGNALING_BLOCK_2:
case TRUNKING_SIGNALING_BLOCK_3:
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilter.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilter.java
index be1df793b..ecd4901a2 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilter.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilter.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,7 +23,7 @@
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlOpcode;
-import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
+import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULCMessage;
import java.util.Collection;
import java.util.function.Function;
@@ -63,7 +63,7 @@ private class KeyExtractor implements Function
@Override
public LinkControlOpcode apply(IMessage message)
{
- if(message instanceof TDULinkControlMessage tdulc)
+ if(message instanceof TDULCMessage tdulc)
{
return tdulc.getLinkControlWord().getOpcode();
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java
index bcfb51e45..e13d81229 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2023 Dennis Sheirer
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,7 +22,7 @@
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlOpcode;
-import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
+import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULCMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDUMessage;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +59,6 @@ public TerminatorMessageFilterSet()
@Override
public boolean canProcess(IMessage message)
{
- return (message instanceof TDUMessage || message instanceof TDULinkControlMessage) && super.canProcess(message);
+ return (message instanceof TDUMessage || message instanceof TDULCMessage) && super.canProcess(message);
}
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HeaderData.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HeaderData.java
index 67f6fdbc2..4ff1eafea 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HeaderData.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HeaderData.java
@@ -1,23 +1,20 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
- * * ******************************************************************************
- * * Copyright (C) 2014-2019 Dennis Sheirer
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU General Public License as published by
- * * the Free Software Foundation, either version 3 of the License, or
- * * (at your option) any later version.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU General Public License for more details.
- * *
- * * You should have received a copy of the GNU General Public License
- * * along with this program. If not, see
- * * *****************************************************************************
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.hdu;
@@ -25,11 +22,11 @@
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
+import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.module.decode.p25.reference.Encryption;
import io.github.dsheirer.module.decode.p25.reference.Vendor;
-
import java.util.ArrayList;
import java.util.List;
@@ -49,8 +46,8 @@ public class HeaderData
private boolean mValid = true;
private BinaryMessage mMessage;
- private Identifier mEncryptionKey;
- private Identifier mTalkgroup;
+ private EncryptionKeyIdentifier mEncryptionKey;
+ private TalkgroupIdentifier mTalkgroup;
private List mIdentifiers;
public HeaderData(BinaryMessage message)
@@ -129,9 +126,9 @@ public Vendor getVendor()
return Vendor.fromValue(getMessage().getInt(VENDOR_ID));
}
- public Identifier getEncryptionKey()
+ public EncryptionKeyIdentifier getEncryptionKey()
{
- if(mEncryptionKey == null && isEncryptedAudio())
+ if(mEncryptionKey == null)
{
mEncryptionKey = EncryptionKeyIdentifier.create(APCO25EncryptionKey.create(getMessage().getInt(ALGORITHM_ID),
getMessage().getInt(KEY_ID)));
@@ -150,7 +147,7 @@ public boolean isEncryptedAudio()
return getEncryption() != Encryption.UNENCRYPTED;
}
- public Identifier getTalkgroup()
+ public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallTermination.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallTermination.java
index db9829dfc..c93e6d110 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallTermination.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCCallTermination.java
@@ -32,7 +32,8 @@
*/
public class LCCallTermination extends LinkControlWord
{
- private static final int MOTOROLA_SYSTEM_CONTROLLER = 0xFFFFFD;
+ private static final int MOTOROLA_SYSTEM_CONTROLLER_1 = 0xFFFFFD;
+ private static final int MOTOROLA_SYSTEM_CONTROLLER_2 = 0xFFFFFF;
private static final int HARRIS_SYSTEM_CONTROLLER = 0;
private static final IntField ADDRESS = IntField.length24(OCTET_6_BIT_48);
private Identifier mAddress;
@@ -75,7 +76,7 @@ public Identifier getAddress()
public boolean isNetworkCommandedTeardown()
{
int address = getInt(ADDRESS);
- return address == MOTOROLA_SYSTEM_CONTROLLER || address == HARRIS_SYSTEM_CONTROLLER;
+ return address == MOTOROLA_SYSTEM_CONTROLLER_1 || address == MOTOROLA_SYSTEM_CONTROLLER_2 || address == HARRIS_SYSTEM_CONTROLLER;
}
/**
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/EncryptionSyncParameters.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/EncryptionSyncParameters.java
index 5bdb9ecf0..c6428d2c1 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/EncryptionSyncParameters.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/EncryptionSyncParameters.java
@@ -1,23 +1,20 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2024 Dennis Sheirer
*
- * * ******************************************************************************
- * * Copyright (C) 2014-2019 Dennis Sheirer
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU General Public License as published by
- * * the Free Software Foundation, either version 3 of the License, or
- * * (at your option) any later version.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU General Public License for more details.
- * *
- * * You should have received a copy of the GNU General Public License
- * * along with this program. If not, see
- * * *****************************************************************************
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.ldu;
@@ -27,7 +24,7 @@
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.encryption.APCO25EncryptionKey;
-
+import io.github.dsheirer.module.decode.p25.reference.Encryption;
import java.util.ArrayList;
import java.util.List;
@@ -36,6 +33,7 @@
*/
public class EncryptionSyncParameters implements IEncryptionSyncParameters
{
+ private static final String EMPTY_MESSAGE_INDICATOR = "000000000000000000";
private static final int[] MESSAGE_INDICATOR_1 = {0, 1, 2, 3, 4, 5, 6, 7};
private static final int[] MESSAGE_INDICATOR_2 = {8, 9, 10, 11, 12, 13, 14, 15};
private static final int[] MESSAGE_INDICATOR_3 = {16, 17, 18, 19, 20, 21, 22, 23};
@@ -134,8 +132,16 @@ public EncryptionKeyIdentifier getEncryptionKey()
{
if(mEncryptionKey == null)
{
- mEncryptionKey = EncryptionKeyIdentifier.create(APCO25EncryptionKey.create(getMessage().getInt(ALGORITHM_ID),
- getMessage().getInt(KEY_ID)));
+ int algorithm = getMessage().getInt(ALGORITHM_ID);
+ int key = getMessage().getInt(KEY_ID);
+
+ //Detect when algorithm, key and MI are all zeros and override algorithm to set as unencrypted.
+ if(algorithm == 0 && key == 0 && getMessageIndicator().contains(EMPTY_MESSAGE_INDICATOR))
+ {
+ algorithm = Encryption.UNENCRYPTED.getValue(); //0x80
+ }
+
+ mEncryptionKey = EncryptionKeyIdentifier.create(APCO25EncryptionKey.create(algorithm, key));
}
return mEncryptionKey;
diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULCMessage.java
similarity index 97%
rename from src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java
rename to src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULCMessage.java
index ab699fed9..57bf7d8b6 100644
--- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULCMessage.java
@@ -31,7 +31,7 @@
import java.util.Collections;
import java.util.List;
-public class TDULinkControlMessage extends P25P1Message implements IFrequencyBandReceiver
+public class TDULCMessage extends P25P1Message implements IFrequencyBandReceiver
{
public static final int[] LC_HEX_0 = {0, 1, 2, 3, 4, 5};
public static final int[] LC_HEX_1 = {6, 7, 8, 9, 10, 11};
@@ -63,7 +63,7 @@ public class TDULinkControlMessage extends P25P1Message implements IFrequencyBan
private LinkControlWord mLinkControlWord;
- public TDULinkControlMessage(CorrectedBinaryMessage message, int nac, long timestamp)
+ public TDULCMessage(CorrectedBinaryMessage message, int nac, long timestamp)
{
super(message, nac, timestamp);
}