Skip to content

Commit

Permalink
#2013 Enhance SUID extension message handling and correct encryption …
Browse files Browse the repository at this point in the history
…sync when the algorithm is detected as ACCORDION and the key ID and MI are both zero, indicating a bad algorithm ID value. (#2014)

Co-authored-by: Dennis Sheirer <[email protected]>
  • Loading branch information
DSheirer and Dennis Sheirer authored Oct 14, 2024
1 parent 67f91b8 commit 97afdfa
Show file tree
Hide file tree
Showing 12 changed files with 404 additions and 176 deletions.
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/

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
Expand Down Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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<Identifier> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -133,7 +133,7 @@ else if (message instanceof HDUMessage)
}
}

private void process(TDULinkControlMessage tdulc)
private void process(TDULCMessage tdulc)
{
process(tdulc.getLinkControlWord());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -190,6 +189,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
private final P25P1NetworkConfigurationMonitor mNetworkConfigurationMonitor;
private final Listener<ChannelEvent> mChannelEventListener;
private P25TrafficChannelManager mTrafficChannelManager;
private ServiceOptions mCurrentServiceOptions;

/**
* Constructs an APCO-25 decoder state with an optional traffic channel manager.
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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));
}
}
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -1831,15 +1842,20 @@ 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:
case UNIT_TO_UNIT_VOICE_CHANNEL_USER:
case UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED:
if(isTerminator)
{
closeCurrentCallEvent(timestamp, lcw.toString());
mTrafficChannelManager.processP1TrafficCallEnd(getCurrentFrequency(), timestamp, lcw.toString());
List<Identifier> updated = mPatchGroupManager.update(lcw.getIdentifiers(), timestamp);
getIdentifierCollection().update(updated);
}
else
{
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
{
Expand Down
Loading

0 comments on commit 97afdfa

Please sign in to comment.