#1914 P25 P1 & P2 Talker Alias Support for Motorola and L3Harris. Talker alias manager caches aliases and applies to future events. Active (cached) talker aliases now listed in the channel Details tab. Enables multi-select in message viewer for all supported protocols. Note: decoding of Motorola's talker alias format not yet implemented - only recognition and reassembly of the encoded sequence is currently supported. (#2001)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2024-10-05 03:47:18 -04:00
committed by GitHub
parent 600db3fb8a
commit 3c8cd0386d
42 changed files with 2047 additions and 445 deletions

View File

@@ -188,7 +188,7 @@ public class DecoderFactory
processPassport(channel, modules, aliasList, decodeConfig);
break;
case P25_PHASE1:
processP25Phase1(channel, userPreferences, modules, aliasList, channelType, (DecodeConfigP25Phase1) decodeConfig);
processP25Phase1(channel, userPreferences, modules, aliasList, trafficChannelManager);
break;
case P25_PHASE2:
processP25Phase2(channel, userPreferences, modules, aliasList, trafficChannelManager);
@@ -262,32 +262,37 @@ public class DecoderFactory
* @param modules collection to add to
* @param aliasList for the channel
*/
private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigP25Phase1 decodeConfig)
private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List<Module> modules,
AliasList aliasList, TrafficChannelManager trafficChannelManager)
{
DecodeConfigP25Phase1 p25Config = decodeConfig;
switch(p25Config.getModulation())
if(channel.getDecodeConfiguration() instanceof DecodeConfigP25Phase1 p1)
{
case C4FM:
modules.add(new P25P1DecoderC4FM());
break;
case CQPSK:
modules.add(new P25P1DecoderLSM());
break;
default:
throw new IllegalArgumentException("Unrecognized P25 Phase 1 Modulation [" +
p25Config.getModulation() + "]");
switch(p1.getModulation())
{
case C4FM:
modules.add(new P25P1DecoderC4FM());
break;
case CQPSK:
modules.add(new P25P1DecoderLSM());
break;
default:
throw new IllegalArgumentException("Unrecognized P25 Phase 1 Modulation [" + p1.getModulation() + "]");
}
}
if(channelType == ChannelType.STANDARD)
if(channel.getChannelType() == ChannelType.STANDARD)
{
P25TrafficChannelManager trafficChannelManager = new P25TrafficChannelManager(channel);
modules.add(trafficChannelManager);
modules.add(new P25P1DecoderState(channel, trafficChannelManager));
P25TrafficChannelManager primaryTCM = new P25TrafficChannelManager(channel);
modules.add(primaryTCM);
modules.add(new P25P1DecoderState(channel, primaryTCM));
}
else if(trafficChannelManager instanceof P25TrafficChannelManager parentTCM)
{
modules.add(new P25P1DecoderState(channel, parentTCM));
}
else
{
modules.add(new P25P1DecoderState(channel));
mLog.warn("Expected non-null traffic channel manager for channel " + channel.getName());
}
modules.add(new P25P1AudioModule(userPreferences, aliasList));

View File

@@ -1457,12 +1457,20 @@ public class DMRDecoderState extends TimeslotDecoderState
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
if(mNetworkConfigurationMonitor != null)
{
return mNetworkConfigurationMonitor.getActivitySummary();
sb.append(mNetworkConfigurationMonitor.getActivitySummary());
sb.append("\n\n");
sb.append(mTrafficChannelManager.getTalkerAliasManager().getAliasSummary());
}
else
{
sb.append(mTrafficChannelManager.getTalkerAliasManager().getAliasSummary());
}
return "";
return sb.toString();
}
@Override

View File

@@ -31,6 +31,7 @@ import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.TalkerAliasManager;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.MessageHistoryPreloadData;
import io.github.dsheirer.message.MessageHistoryRequest;
@@ -103,6 +104,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
private Listener<ChannelEvent> mChannelEventListener;
private Listener<IDecodeEvent> mDecodeEventListener;
private TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor();
private TalkerAliasManager mTalkerAliasManager = new TalkerAliasManager();
private Channel mParentChannel;
private boolean mIgnoreDataCalls;
@@ -129,6 +131,14 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
createTrafficChannels();
}
/**
* Talker alias manager
*/
public TalkerAliasManager getTalkerAliasManager()
{
return mTalkerAliasManager;
}
/**
* Sets the current parent control channel frequency so that channel grants for the current frequency do not
* produce an additional traffic channel allocation.

View File

@@ -70,9 +70,6 @@ public class TalkerAliasHeader extends FullLCMessage
*/
public int getTotalBitLength()
{
TalkerAliasDataFormat format = getFormat();
int bitsPerCharacter = format.getBitsPerCharacter();
int characterLength = getCharacterLength();
int total = getFormat().getBitsPerCharacter() * getCharacterLength();
//Max payload bit length is 217 (49 + 56 + 56 + 56)

View File

@@ -30,6 +30,7 @@ import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.alias.TalkerAliasManager;
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroupPreLoadDataContent;
import io.github.dsheirer.identifier.scramble.ScrambleParameterIdentifier;
@@ -118,6 +119,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
//Used only for data calls
private DecodeEventDuplicateDetector mDuplicateDetector = new DecodeEventDuplicateDetector();
private ReentrantLock mLock = new ReentrantLock();
private TalkerAliasManager mTalkerAliasManager = new TalkerAliasManager();
/**
* Constructs an instance.
@@ -141,6 +143,15 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
}
}
/**
* Talker alias manager
* @return manager
*/
public TalkerAliasManager getTalkerAliasManager()
{
return mTalkerAliasManager;
}
/**
* Stores the frequency band (aka Identifier Update) to use for preload data in starting a new traffic channel.
* @param frequencyBand to store

View File

@@ -1,28 +1,24 @@
/*
* *****************************************************************************
* 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.module.decode.p25.identifier.talkgroup;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
/**
* APCO25 Announcement Talkgroup - grouping of talkgroups within a system.
@@ -42,7 +38,7 @@ public class APCO25AnnouncementTalkgroup extends APCO25Talkgroup
/**
* Creates an APCO-25 announcement group talkgroup identifier
*/
public static TalkgroupIdentifier create(int talkgroup)
public static APCO25Talkgroup create(int talkgroup)
{
return new APCO25AnnouncementTalkgroup(talkgroup);
}

View File

@@ -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 <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.module.decode.p25.identifier.talkgroup;
@@ -47,7 +44,7 @@ public class APCO25Talkgroup extends TalkgroupIdentifier
/**
* Creates an APCO-25 TO talkgroup identifier
*/
public static TalkgroupIdentifier create(int talkgroup)
public static APCO25Talkgroup create(int talkgroup)
{
return new APCO25Talkgroup(talkgroup);
}

View File

@@ -38,6 +38,7 @@ import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroupManager;
import io.github.dsheirer.identifier.patch.PatchGroupPreLoadDataContent;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.log.LoggingSuppressor;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
@@ -62,10 +63,12 @@ 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.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;
@@ -181,8 +184,8 @@ import org.slf4j.LoggerFactory;
*/
public class P25P1DecoderState extends DecoderState implements IChannelEventListener
{
private static final Logger mLog = LoggerFactory.getLogger(P25P1DecoderState.class);
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(mLog);
private static final Logger LOGGER = LoggerFactory.getLogger(P25P1DecoderState.class);
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LOGGER);
private final Channel mChannel;
private final P25P1Decoder.Modulation mModulation;
private final PatchGroupManager mPatchGroupManager = new PatchGroupManager();
@@ -315,6 +318,30 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
break;
}
}
else if(iMessage instanceof MotorolaTalkerAliasComplete tac)
{
mTrafficChannelManager.getTalkerAliasManager().update(tac.getRadio(), tac.getAlias());
}
else if(iMessage instanceof LCHarrisTalkerAliasComplete talkerAlias)
{
processTalkerAlias(talkerAlias);
}
}
/**
* Process a fully reassembled L3Harris talker alias on the current traffic channel.
* @param talkerAlias reassembled.
*/
private void processTalkerAlias(LCHarrisTalkerAliasComplete talkerAlias)
{
Identifier identifier = getIdentifierCollection().getFromIdentifier();
if(identifier instanceof RadioIdentifier radioIdentifier)
{
mTrafficChannelManager.getTalkerAliasManager().update(radioIdentifier, talkerAlias.getTalkerAlias());
}
mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), talkerAlias.getTalkerAlias(), talkerAlias.getTimestamp());
}
/**
@@ -332,6 +359,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
if(apco25Channel.getValue().getDownlinkFrequency() > 0)
{
MutableIdentifierCollection mic = getMutableIdentifierCollection(identifiers, timestamp);
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(mic);
mTrafficChannelManager.processP1ChannelGrant(apco25Channel, serviceOptions, mic, opcode, timestamp);
}
}
@@ -348,6 +376,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
Opcode opcode, long timestamp)
{
MutableIdentifierCollection mic = getMutableIdentifierCollection(identifiers, timestamp);
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(mic);
mTrafficChannelManager.processP1ChannelUpdate(channel, serviceOptions, mic, opcode, timestamp);
}
@@ -399,6 +428,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
serviceOptions = VoiceServiceOptions.createUnencrypted();
}
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(getIdentifierCollection());
mTrafficChannelManager.processP1CurrentUser(getCurrentFrequency(), getCurrentChannel(), decodeEventType,
serviceOptions, getIdentifierCollection(), timestamp, null );
@@ -445,6 +475,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
{
MutableIdentifierCollection mic = getMutableIdentifierCollection(identifiers, timestamp);
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(mic);
broadcast(P25DecodeEvent.builder(decodeEventType, timestamp)
.channel(getCurrentChannel())
.details(details)
@@ -675,7 +707,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
//don't allow that to corrupt the real frequency bands for this system.
break;
default:
// mLog.debug("Unrecognized AMBTC Opcode: " + ambtc.getHeader().getOpcode().name());
// LOGGER.debug("Unrecognized AMBTC Opcode: " + ambtc.getHeader().getOpcode().name());
break;
}
}
@@ -802,6 +834,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
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);
@@ -1960,6 +1993,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
case ADJACENT_SITE_STATUS_BROADCAST:
case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT:
case CHANNEL_IDENTIFIER_UPDATE:
case CHANNEL_IDENTIFIER_UPDATE_VU:
case PROTECTION_PARAMETER_BROADCAST:
case SECONDARY_CONTROL_CHANNEL_BROADCAST:
case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT:
@@ -1991,8 +2026,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
null, timestamp);
}
break;
case MOTOROLA_RADIO_REPROGRAM_HEADER:
case MOTOROLA_RADIO_REPROGRAM_RECORD:
case MOTOROLA_TALKER_ALIAS_HEADER:
case MOTOROLA_TALKER_ALIAS_DATA_BLOCK:
if(isTerminator)
{
closeCurrentCallEvent(timestamp);
@@ -2161,10 +2196,14 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
{
closeCurrentCallEvent(timestamp);
}
// LOGGING_SUPPRESSOR.info(lcw.getVendor().toString() + lcw.getOpcodeNumber() + lcw.getMessage().toHexString(),
// 1, "Unrecognized LCW Opcode: " + lcw.getOpcode().name() + " VENDOR:" + lcw.getVendor() +
// " OPCODE:" + lcw.getOpcodeNumber() + " MSG:" + lcw.getMessage().toHexString() +
// " CHAN:" + getCurrentChannel() + " FREQ:" + getCurrentFrequency());
// if(lcw.getVendor().isLoggable())
// {
// LOGGING_SUPPRESSOR.info(lcw.getVendor().toString() + lcw.getOpcodeNumber() + lcw.getMessage().toHexString(),
// 1, "Unrecognized LCW Opcode: " + lcw.getOpcode().name() + " VENDOR:" + lcw.getVendor() +
// " OPCODE:" + lcw.getOpcodeNumber() + " MSG:" + lcw.getMessage().toHexString() +
// " CHAN:" + getCurrentChannel() + " FREQ:" + getCurrentFrequency());
// }
break;
}
}
@@ -2186,6 +2225,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
sb.append(mNetworkConfigurationMonitor.getActivitySummary());
sb.append("\n");
sb.append(mPatchGroupManager.getPatchGroupSummary());
sb.append("\n");
sb.append(mTrafficChannelManager.getTalkerAliasManager().getAliasSummary());
return sb.toString();
}

View File

@@ -25,6 +25,8 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.ExtendedSourceLinkControlWord;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.HarrisTalkerAliasAssembler;
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;
@@ -38,7 +40,7 @@ import org.slf4j.LoggerFactory;
/**
* APCO25 Phase 1 Message Processor.
*
* Performs post-message creation processing before the message is sent downstream.
* Performs post-message creation processing and enrichment before the message is sent downstream.
*/
public class P25P1MessageProcessor implements Listener<IMessage>
{
@@ -60,6 +62,19 @@ public class P25P1MessageProcessor implements Listener<IMessage>
*/
private ExtendedSourceLinkControlWord mExtendedSourceLinkControlWord;
/**
* Motorola talker alias assembler for link control header and data blocks.
*/
private LCMotorolaTalkerAliasAssembler mMotorolaTalkerAliasAssembler = new LCMotorolaTalkerAliasAssembler();
/**
* Harris talker alias assembler for link control talker alias blocks
*/
private HarrisTalkerAliasAssembler mHarrisTalkerAliasAssembler = new HarrisTalkerAliasAssembler();
/**
* Constructs an instance
*/
public P25P1MessageProcessor()
{
}
@@ -79,19 +94,40 @@ public class P25P1MessageProcessor implements Listener<IMessage>
}
}
/**
* Processes the message for enrichment or reassembly of fragments and sends the enriched message and any additional
* messages that were created during the enrichment to the registered message listener.
* @param message to process
*/
@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)
{
reassembleLC(ldu.getLinkControlWord());
//Send the LCW to the harris talker alias assembler
additionalMessageToSend = mHarrisTalkerAliasAssembler.process(ldu.getLinkControlWord(), ldu.getTimestamp());
}
else if(message instanceof TDULinkControlMessage tdu)
{
reassembleLC(tdu.getLinkControlWord());
LinkControlWord lcw = tdu.getLinkControlWord();
reassembleLC(lcw);
//Motorola carries the talker alias in the TDULC
if(mMotorolaTalkerAliasAssembler.add(lcw, message.getTimestamp()))
{
additionalMessageToSend = mMotorolaTalkerAliasAssembler.assemble();
}
//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 */
@@ -119,13 +155,25 @@ public class P25P1MessageProcessor implements Listener<IMessage>
if(message instanceof IFrequencyBand)
{
IFrequencyBand bandIdentifier = (IFrequencyBand)message;
mFrequencyBandMap.put(bandIdentifier.getIdentifier(), bandIdentifier);
//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);
}
}
}
if(mMessageListener != null)
{
mMessageListener.receive(message);
if(additionalMessageToSend != null)
{
mMessageListener.receive(additionalMessageToSend);
}
}
}
@@ -153,17 +201,27 @@ public class P25P1MessageProcessor implements Listener<IMessage>
}
}
/**
* Prepares for disposal of this instance.
*/
public void dispose()
{
mFrequencyBandMap.clear();
mMessageListener = null;
}
/**
* Sets the message listener to receive the output from this processor.
* @param listener to receive output messages
*/
public void setMessageListener(Listener<IMessage> listener)
{
mMessageListener = listener;
}
/**
* Clears a registered message listener.
*/
public void removeMessageListener()
{
mMessageListener = null;

View File

@@ -31,21 +31,6 @@ import io.github.dsheirer.protocol.Protocol;
*/
public abstract class P25P1Message extends TimeslotMessage implements IMessage
{
//P25 Phase 1 ICD defines octets starting at zero.
public static final int OCTET_0_BIT_0 = 0;
public static final int OCTET_1_BIT_8 = 8;
public static final int OCTET_2_BIT_16 = 16;
public static final int OCTET_3_BIT_24 = 24;
public static final int OCTET_4_BIT_32 = 32;
public static final int OCTET_5_BIT_40 = 40;
public static final int OCTET_6_BIT_48 = 48;
public static final int OCTET_7_BIT_56 = 56;
public static final int OCTET_8_BIT_64 = 64;
public static final int OCTET_9_BIT_72 = 72;
public static final int OCTET_10_BIT_80 = 80;
public static final int OCTET_11_BIT_88 = 88;
public static final int OCTET_12_BIT_96 = 96;
private Identifier mNAC;
/**

View File

@@ -21,6 +21,7 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc;
import io.github.dsheirer.module.decode.p25.reference.Vendor;
import java.util.EnumSet;
import java.util.logging.Logger;
/**
* Opcodes used for Link Control Words.
@@ -99,19 +100,27 @@ public enum LinkControlOpcode
MOTOROLA_UNIT_GPS("MOTOROLA UNIT GPS", 6),
MOTOROLA_EMERGENCY_ALARM_ACTIVATION("MOTOROLA EMERGENCY ALARM ACTIVATION", 10),
MOTOROLA_TALK_COMPLETE("MOTOROLA TALK_COMPLETE", 15),
MOTOROLA_RADIO_REPROGRAM_HEADER("MOTOROLA RADIO REPROGRAM HEADER", 21),
MOTOROLA_RADIO_REPROGRAM_RECORD("MOTOROLA RADIO REPROGRAM CONTINUATION", 23),
MOTOROLA_TALKER_ALIAS_HEADER("MOTOROLA TALKER ALIAS HEADER", 21),
MOTOROLA_TALKER_ALIAS_DATA_BLOCK("MOTOROLA TALKER ALIAS DATA BLOCK", 23),
MOTOROLA_UNKNOWN("MOTOROLA UNKNOWN", -1),
L3HARRIS_RETURN_TO_CONTROL_CHANNEL("UNKNOWN OPCODE 10", 10),
L3HARRIS_UNKNOWN_2A("UNKNOWN OPCODE 42", 42),
L3HARRIS_UNKNOWN_2B("UNKNOWN OPCODE 43", 43),
L3HARRIS_TALKER_ALIAS_BLOCK_1("TALKER ALIAS BLOCK 1", 50),
L3HARRIS_TALKER_ALIAS_BLOCK_2("TALKER ALIAS BLOCK 2", 51),
L3HARRIS_TALKER_ALIAS_BLOCK_3("TALKER ALIAS BLOCK 3", 52),
L3HARRIS_TALKER_ALIAS_BLOCK_4("TALKER ALIAS BLOCK 4", 53),
L3HARRIS_UNKNOWN("L3HARRIS UNKNOWN", -1),
//This is used for reassembled talker aliases and is not an actual opcode
TALKER_ALIAS_COMPLETE("TALKER ALIAS COMPLETE", -1),
UNKNOWN("UNKNOWN", -1);
private String mLabel;
private int mCode;
private static Logger LOGGER = Logger.getLogger(LinkControlOpcode.class.getName());
/**
* Constructor
@@ -142,7 +151,8 @@ public enum LinkControlOpcode
* L3Harris Opcodes
*/
public static final EnumSet<LinkControlOpcode> L3HARRIS_OPCODES = EnumSet.of(L3HARRIS_RETURN_TO_CONTROL_CHANNEL,
L3HARRIS_UNKNOWN_2A, L3HARRIS_UNKNOWN_2B, L3HARRIS_UNKNOWN);
L3HARRIS_UNKNOWN_2A, L3HARRIS_UNKNOWN_2B, L3HARRIS_TALKER_ALIAS_BLOCK_1, L3HARRIS_TALKER_ALIAS_BLOCK_2,
L3HARRIS_TALKER_ALIAS_BLOCK_3, L3HARRIS_TALKER_ALIAS_BLOCK_4, L3HARRIS_UNKNOWN);
/**
* Network/channel related opcodes
@@ -213,6 +223,14 @@ public enum LinkControlOpcode
return L3HARRIS_UNKNOWN_2A;
case 43:
return L3HARRIS_UNKNOWN_2B;
case 50:
return L3HARRIS_TALKER_ALIAS_BLOCK_1;
case 51:
return L3HARRIS_TALKER_ALIAS_BLOCK_2;
case 52:
return L3HARRIS_TALKER_ALIAS_BLOCK_3;
case 53:
return L3HARRIS_TALKER_ALIAS_BLOCK_4;
default:
return L3HARRIS_UNKNOWN;
}
@@ -234,9 +252,9 @@ public enum LinkControlOpcode
case 15:
return MOTOROLA_TALK_COMPLETE;
case 21:
return MOTOROLA_RADIO_REPROGRAM_HEADER;
return MOTOROLA_TALKER_ALIAS_HEADER;
case 23:
return MOTOROLA_RADIO_REPROGRAM_RECORD;
return MOTOROLA_TALKER_ALIAS_DATA_BLOCK;
default:
return MOTOROLA_UNKNOWN;
}

View File

@@ -21,6 +21,10 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisReturnToControlChannel;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasBlock1;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasBlock2;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasBlock3;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasBlock4;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode42;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode43;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaEmergencyAlarmActivation;
@@ -28,9 +32,9 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorol
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupAdd;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUpdate;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUser;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaRadioReprogramHeader;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaRadioReprogramRecord;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkComplete;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkerAliasDataBlock;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkerAliasHeader;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnitGPS;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnknownOpcode;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCAdjacentSiteStatusBroadcast;
@@ -158,6 +162,14 @@ public class LinkControlWordFactory
return new LCHarrisUnknownOpcode42(message);
case L3HARRIS_UNKNOWN_2B:
return new LCHarrisUnknownOpcode43(message);
case L3HARRIS_TALKER_ALIAS_BLOCK_1:
return new LCHarrisTalkerAliasBlock1(message);
case L3HARRIS_TALKER_ALIAS_BLOCK_2:
return new LCHarrisTalkerAliasBlock2(message);
case L3HARRIS_TALKER_ALIAS_BLOCK_3:
return new LCHarrisTalkerAliasBlock3(message);
case L3HARRIS_TALKER_ALIAS_BLOCK_4:
return new LCHarrisTalkerAliasBlock4(message);
case L3HARRIS_UNKNOWN:
return new UnknownLinkControlWord(message);
@@ -173,10 +185,10 @@ public class LinkControlWordFactory
return new LCMotorolaGroupRegroupVoiceChannelUpdate(message);
case MOTOROLA_UNIT_GPS:
return new LCMotorolaUnitGPS(message);
case MOTOROLA_RADIO_REPROGRAM_HEADER:
return new LCMotorolaRadioReprogramHeader(message);
case MOTOROLA_RADIO_REPROGRAM_RECORD:
return new LCMotorolaRadioReprogramRecord(message);
case MOTOROLA_TALKER_ALIAS_HEADER:
return new LCMotorolaTalkerAliasHeader(message);
case MOTOROLA_TALKER_ALIAS_DATA_BLOCK:
return new LCMotorolaTalkerAliasDataBlock(message);
case MOTOROLA_EMERGENCY_ALARM_ACTIVATION:
return new LCMotorolaEmergencyAlarmActivation(message);
case MOTOROLA_UNKNOWN:

View File

@@ -0,0 +1,116 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
/**
* Assembles a talker alias from talker alias blocks 1-4
*/
public class HarrisTalkerAliasAssembler
{
private LCHarrisTalkerAliasBlock1 mBlock1;
private LCHarrisTalkerAliasBlock2 mBlock2;
private LCHarrisTalkerAliasBlock3 mBlock3;
private LCHarrisTalkerAliasBlock4 mBlock4;
private long mTimestamp;
/**
* Constructs an instance.
*/
public HarrisTalkerAliasAssembler()
{
}
/**
* Resets the contents when a terminator or idle message is received.
*/
public void reset()
{
mBlock1 = null;
mBlock2 = null;
mBlock3 = null;
mBlock4 = null;
}
/**
* Processes the FLC talker alias message and returns a fully assembled alias when at least 2 fragments are received.
* @param lcw containing Harris LC talker alias blocks 1-4
* @return fully assembled talker alias, if available, or null.
*/
public LCHarrisTalkerAliasComplete process(LinkControlWord lcw, long timestamp)
{
mTimestamp = timestamp;
if(lcw.isValid())
{
switch(lcw.getOpcode())
{
case L3HARRIS_TALKER_ALIAS_BLOCK_1:
if(lcw instanceof LCHarrisTalkerAliasBlock1 block1)
{
mBlock1 = block1;
return assemble();
}
break;
case L3HARRIS_TALKER_ALIAS_BLOCK_2:
if(lcw instanceof LCHarrisTalkerAliasBlock2 block2)
{
mBlock2 = block2;
return assemble();
}
break;
case L3HARRIS_TALKER_ALIAS_BLOCK_3:
if(lcw instanceof LCHarrisTalkerAliasBlock3 block3)
{
mBlock3 = block3;
return assemble();
}
break;
case L3HARRIS_TALKER_ALIAS_BLOCK_4:
if(lcw instanceof LCHarrisTalkerAliasBlock4 block4)
{
mBlock4 = block4;
return assemble();
}
break;
}
}
return null;
}
/**
* Assembles a complete alias from the 4 fragment blocks
* @return assembled alias or null.
*/
private LCHarrisTalkerAliasComplete assemble()
{
if(mBlock1 != null && mBlock2 != null)
{
String fragment2 = mBlock2.getPayloadFragmentString();
String fragment3 = mBlock3 != null ? mBlock3.getPayloadFragmentString() : null;
String fragment4 = mBlock4 != null ? mBlock4.getPayloadFragmentString() : null;
return new LCHarrisTalkerAliasComplete(mBlock1.getMessage(), fragment2, fragment3, fragment4, mTimestamp);
}
return null;
}
}

View File

@@ -0,0 +1,72 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import java.util.Collections;
import java.util.List;
/**
* Base class for L3Harris talker alias messages
*/
public abstract class LCHarrisTalkerAliasBase extends LinkControlWord
{
private static final int PAYLOAD_START = OCTET_2_BIT_16;
private static final int PAYLOAD_END = OCTET_9_BIT_72;
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisTalkerAliasBase(CorrectedBinaryMessage message)
{
super(message);
}
/**
* Payload fragment carried by the header.
* @return payload fragment.
*/
public CorrectedBinaryMessage getPayloadFragment()
{
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
}
/**
* Payload fragment as a string with empty space removed.
* @return fragment
*/
public String getPayloadFragmentString()
{
return new String(getPayloadFragment().toByteArray()).trim();
}
/**
* List of identifiers contained in this message
*/
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,59 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* L3Harris Opcode 50 (0x32) Talker Alias Block 1 (of 4).
*/
public class LCHarrisTalkerAliasBlock1 extends LCHarrisTalkerAliasBase
{
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisTalkerAliasBlock1(CorrectedBinaryMessage message)
{
super(message);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("**CRC-FAILED** ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
else
{
sb.append("L3HARRIS TALKER ALIAS FRAGMENT 1/4:").append(getPayloadFragmentString());
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
}

View File

@@ -0,0 +1,62 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* L3Harris Opcode 51 (0x33) Talker Alias Block 2.
*/
public class LCHarrisTalkerAliasBlock2 extends LCHarrisTalkerAliasBase
{
private static final int PAYLOAD_START = OCTET_2_BIT_16;
private static final int PAYLOAD_END = OCTET_9_BIT_72;
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisTalkerAliasBlock2(CorrectedBinaryMessage message)
{
super(message);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("**CRC-FAILED** ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
else
{
sb.append("L3HARRIS TALKER ALIAS FRAGMENT 2/4:").append(getPayloadFragmentString());
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
}

View File

@@ -0,0 +1,62 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* L3Harris Opcode 52 (0x34) Talker Alias Block 3.
*/
public class LCHarrisTalkerAliasBlock3 extends LCHarrisTalkerAliasBase
{
private static final int PAYLOAD_START = OCTET_2_BIT_16;
private static final int PAYLOAD_END = OCTET_9_BIT_72;
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisTalkerAliasBlock3(CorrectedBinaryMessage message)
{
super(message);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("**CRC-FAILED** ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
else
{
sb.append("L3HARRIS TALKER ALIAS FRAGMENT 3/4:").append(getPayloadFragmentString());
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
}

View File

@@ -0,0 +1,62 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* L3Harris Opcode 53 (0x35) Talker Alias Block 4.
*/
public class LCHarrisTalkerAliasBlock4 extends LCHarrisTalkerAliasBase
{
private static final int PAYLOAD_START = OCTET_2_BIT_16;
private static final int PAYLOAD_END = OCTET_9_BIT_72;
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCHarrisTalkerAliasBlock4(CorrectedBinaryMessage message)
{
super(message);
}
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("**CRC-FAILED** ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
else
{
sb.append("L3HARRIS TALKER ALIAS FRAGMENT 4/4:").append(getPayloadFragmentString());
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
}

View File

@@ -0,0 +1,162 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.alias.P25TalkerAliasIdentifier;
import io.github.dsheirer.identifier.alias.TalkerAliasIdentifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlOpcode;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.protocol.Protocol;
import java.util.ArrayList;
import java.util.List;
/**
* L3Harris talker alias complete. This is a talker alias fully reassembled from data blocks 1-4
*/
public class LCHarrisTalkerAliasComplete extends LinkControlWord implements IMessage
{
private static final int PAYLOAD_START = OCTET_2_BIT_16;
private static final int PAYLOAD_END = OCTET_9_BIT_72;
private String mFragment2;
private String mFragment3;
private String mFragment4;
private TalkerAliasIdentifier mTalkerAlias;
private long mTimestamp;
/**
* Constructs an instance
* @param correctedBinaryMessage that is LCW for block 1
* @param fragment2 alias fragment from block 2
* @param fragment3 alias fragment from block 3
* @param fragment4 alias fragment from block 4
* @param timestamp of the most recent LDU and LCW message
*/
public LCHarrisTalkerAliasComplete(CorrectedBinaryMessage correctedBinaryMessage, String fragment2, String fragment3, String fragment4, long timestamp)
{
super(correctedBinaryMessage);
mFragment2 = fragment2;
mFragment3 = fragment3;
mFragment4 = fragment4;
mTimestamp = timestamp;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("L3HARRIS TALKER ALIAS REASSEMBLED:").append(getTalkerAlias());
return sb.toString();
}
@Override
public LinkControlOpcode getOpcode()
{
return LinkControlOpcode.TALKER_ALIAS_COMPLETE;
}
@Override
public int getOpcodeNumber()
{
return LinkControlOpcode.TALKER_ALIAS_COMPLETE.getCode();
}
/**
* Fully (or partially) reassembled talker alias.
*
* Note: in real world examples, data blocks 1-2 have the alias and data blocks 3-4 repeat the same content as
* data blocks 1-2, so we inspect each data block fragment to see if that fragment is already contained in the
* talker alias as we reconstruct it from the fragments and don't add any content that's already in the assembly.
* @return alias.
*/
public TalkerAliasIdentifier getTalkerAlias()
{
if(mTalkerAlias == null)
{
String alias = getPayloadFragmentString();
if(mFragment2 != null && !alias.contains(mFragment2))
{
alias += mFragment2;
if(mFragment3 != null && !alias.contains(mFragment3))
{
alias += mFragment3;
if(mFragment4 != null && !alias.contains(mFragment4))
{
alias += mFragment4;
}
}
}
mTalkerAlias = P25TalkerAliasIdentifier.create(alias);
}
return mTalkerAlias;
}
/**
* Payload fragment carried by the header.
* @return payload fragment.
*/
public CorrectedBinaryMessage getPayloadFragment()
{
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
}
/**
* Payload fragment as a string with empty space removed.
* @return fragment
*/
public String getPayloadFragmentString()
{
return new String(getPayloadFragment().toByteArray()).trim();
}
@Override
public long getTimestamp()
{
return mTimestamp;
}
@Override
public Protocol getProtocol()
{
return Protocol.APCO25;
}
@Override
public int getTimeslot()
{
return P25P1Message.TIMESLOT_0;
}
@Override
public List<Identifier> getIdentifiers()
{
List<Identifier> identifiers = new ArrayList<>();
identifiers.add(getTalkerAlias());
return identifiers;
}
}

View File

@@ -0,0 +1,147 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.message.TimeslotMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.protocol.Protocol;
import java.util.HashMap;
import java.util.Map;
/**
* Reassembles a P25 Motorola talker alias from a Header and Data Blocks.
*
* This is used for traffic channels and monitors:
* - Phase 1: link control messaging stream.
* - Phase 2: mac messaging
*/
public class LCMotorolaTalkerAliasAssembler
{
private static final int DATA_BLOCK_FRAGMENT_LENGTH = 44;
private LCMotorolaTalkerAliasHeader mHeader;
private Map<Integer, LCMotorolaTalkerAliasDataBlock> mDataBlocks = new HashMap<>();
private int mSequence = -1;
private long mMostRecentTimestamp;
/**
* Constructs an instance
*/
public LCMotorolaTalkerAliasAssembler()
{
}
/**
* Link control word to process.
* @param lcw to add
* @return true if we can (now) assemble a complete talker alias from the header and data blocks.
*/
public boolean add(LinkControlWord lcw, long timestamp)
{
if(lcw instanceof LCMotorolaTalkerAliasHeader header)
{
mMostRecentTimestamp = timestamp;
if(mSequence != header.getSequence())
{
mDataBlocks.clear();
mSequence = header.getSequence();
}
mHeader = header;
}
else if(lcw instanceof LCMotorolaTalkerAliasDataBlock block)
{
mMostRecentTimestamp = timestamp;
if(block.getSequence() != mSequence)
{
mHeader = null;
mDataBlocks.clear();
mSequence = block.getSequence();
}
mDataBlocks.put(block.getBlockNumber(), block);
}
else
{
return false; //For all lcw's that are not headers or data blocks
}
return isComplete();
}
/**
* Indicates if the assembler has a header and the correct number of data blocks to reassemble an alias.
*/
private boolean isComplete()
{
if(mHeader != null)
{
int dataBlockCount = mHeader.getBlockCount();
for(int x = 1; x <= dataBlockCount; x++)
{
if(!mDataBlocks.containsKey(x))
{
return false;
}
}
return true;
}
return false;
}
/**
* Assembles the talker alias once the header and data blocks have been collected.
*
* Note: do not invoke unless the add() methods indicate that the assembler is complete.
* @return assembled talker alias
* @throws IllegalStateException if the assembler can't assemble the alias.
*/
public MotorolaTalkerAliasComplete assemble() throws IllegalStateException
{
if(!isComplete())
{
throw new IllegalStateException("Can't assemble talker alias - missing header or data block(s)");
}
int dataBlockCount = mHeader.getBlockCount();
CorrectedBinaryMessage reassembled = new CorrectedBinaryMessage(dataBlockCount * DATA_BLOCK_FRAGMENT_LENGTH);
int offset = 0;
for(int x = 1; x <= dataBlockCount; x++)
{
LCMotorolaTalkerAliasDataBlock block = mDataBlocks.get(x);
reassembled.load(offset, block.getFragment());
offset += DATA_BLOCK_FRAGMENT_LENGTH;
}
MotorolaTalkerAliasComplete complete = new MotorolaTalkerAliasComplete(reassembled, mHeader.getTalkgroup(),
mHeader.getSequence(), TimeslotMessage.TIMESLOT_0, mMostRecentTimestamp, Protocol.APCO25);
mHeader = null;
mDataBlocks.clear();
return complete;
}
}

View File

@@ -19,6 +19,7 @@
package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.bits.IntField;
import io.github.dsheirer.identifier.Identifier;
@@ -30,17 +31,19 @@ import java.util.List;
* Motorola Link Control opcode 0x17 (23). This is possibly a radio reprogramming record segment that is used
* in combination with LCO 0x15. See notes in header of LCMotorolaRadioReprogramHeader class.
*/
public class LCMotorolaRadioReprogramRecord extends LinkControlWord
public class LCMotorolaTalkerAliasDataBlock extends LinkControlWord
{
private static final IntField RECORD_NUMBER = IntField.length8(OCTET_2_BIT_16);
private static final IntField SEQUENCE_NUMBER = IntField.length4(OCTET_3_BIT_24);
private static final IntField BLOCK_NUMBER = IntField.length8(OCTET_2_BIT_16);
private static final IntField SEQUENCE = IntField.length4(OCTET_3_BIT_24);
private static final int FRAGMENT_START = OCTET_3_BIT_24 + 4; //inclusive
private static final int FRAGMENT_END = OCTET_9_BIT_72; //exclusive
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message
*/
public LCMotorolaRadioReprogramRecord(CorrectedBinaryMessage message)
public LCMotorolaTalkerAliasDataBlock(CorrectedBinaryMessage message)
{
super(message);
}
@@ -59,28 +62,38 @@ public class LCMotorolaRadioReprogramRecord extends LinkControlWord
sb.append(" ENCRYPTED");
}
sb.append("MOTOROLA RADIO REPROGRAM RECORD:").append(getRecordNumber());
sb.append(" OF SEQUENCE:").append(getSequenceNumber());
sb.append("MOTOROLA TALKER ALIAS DATA BLOCK:").append(getBlockNumber());
sb.append(" OF SEQUENCE:").append(getSequence());
sb.append(" FRAGMENT:").append(getFragment().toHexString());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Record number
* Fragment of encoded alias.
*/
public int getRecordNumber()
public BinaryMessage getFragment()
{
return getInt(RECORD_NUMBER);
return getMessage().getSubMessage(getOffset() + FRAGMENT_START, getOffset() + FRAGMENT_END);
}
/**
* Sequence number
* Block number
*/
public int getSequenceNumber()
public int getBlockNumber()
{
return getInt(SEQUENCE_NUMBER);
return getInt(BLOCK_NUMBER);
}
/**
* Sequence number that identifies the header and data blocks as all part of the same sequence.
*/
public int getSequence()
{
return getInt(SEQUENCE);
}
@Override
public List<Identifier> getIdentifiers()
{

View File

@@ -28,23 +28,18 @@ import java.util.ArrayList;
import java.util.List;
/**
* Motorola Link Control Opcode 0x15.
*
* Note: I suspect this is some form of radio reprogramming message, possibly used to update encryption.
* Motorola Talker Alias Header - Link Control Opcode 0x15.
*
* I've seen LCOpcode 0x15 and 0x17 twice and in both cases it was sent in a TDULC at the end of a call for the same
* radio on the CNYICC system. The header references the talkgroup that the radio is calling, so it may be some form
* of dynamic regrouping, but the total content is quite large.
*
* Note: this seems to only be targeted to certain radios since it wasn't sent at the end of every call.
* radio on the CNYICC system.
*
* Examples observed on traffic channels in TDULC at the end of a call following the Motorola Talk Complete message
* for Radio: 15,104,082 (0xE67852) and TG:7101 (0x1BBD)
* Example 1
*
* 1590 1BBD 07 0100 F 8BA / 1BBD=TG, 07=record count, 0100=?, F=sequence number, 8BA=crc checksum
* 1590 1BBD 07 0100 F 8BA / 1BBD=TG, 07=record count, 0100=Format/Unicode?, F=sequence number, 8BA=crc checksum
* 1790 01 F BEE00 2AE E67 / BEE00=WACN, 2AE=SYS, E67...= RADIO ID
* 1790 02 F 852 83ED1081 / ...852=RADIO ID cont.
* 1790 02 F 852 83ED1081 / ...852=RADIO ID cont. Encoded alias ...
* 1790 03 F E33C03E9B3E
* 1790 04 F 35647DE0C00
* 1790 05 F C8A83E351E4
@@ -68,13 +63,15 @@ import java.util.List;
*
* The reprogramming payload sequence starts by sending the full SUID for the radio (BEE00.2AE.E67852).
*/
public class LCMotorolaRadioReprogramHeader extends LinkControlWord
public class LCMotorolaTalkerAliasHeader extends LinkControlWord
{
private static final IntField TALKGROUP = IntField.length16(OCTET_2_BIT_16);
private static final IntField RECORD_COUNT = IntField.length8(OCTET_4_BIT_32);
private static final IntField SEQUENCE_NUMBER = IntField.length4(OCTET_7_BIT_56);
private static final IntField SEQUENCE_CHECKSUM = IntField.length12(OCTET_7_BIT_56 + 4);
private Identifier mTalkgroup;
private static final IntField BLOCK_COUNT = IntField.length8(OCTET_4_BIT_32);
private static final IntField FORMAT = IntField.length8(OCTET_5_BIT_40); //Value 1 observed - unicode?
private static final IntField UNKNOWN = IntField.length8(OCTET_6_BIT_48); //Always 0x00
private static final IntField SEQUENCE = IntField.length4(OCTET_7_BIT_56);
private static final IntField CHECKSUM = IntField.length12(OCTET_7_BIT_56 + 4);
private APCO25Talkgroup mTalkgroup;
private List<Identifier> mIdentifiers;
/**
@@ -82,7 +79,7 @@ public class LCMotorolaRadioReprogramHeader extends LinkControlWord
*
* @param message
*/
public LCMotorolaRadioReprogramHeader(CorrectedBinaryMessage message)
public LCMotorolaTalkerAliasHeader(CorrectedBinaryMessage message)
{
super(message);
}
@@ -100,10 +97,12 @@ public class LCMotorolaRadioReprogramHeader extends LinkControlWord
{
sb.append(" ENCRYPTED");
}
sb.append("MOTOROLA RADIO REPROGRAM HEADER");
sb.append("MOTOROLA TALKER ALIAS HEADER");
sb.append(" TG:").append(getTalkgroup());
sb.append(" RECORD COUNT:").append(getRecordCount());
sb.append(" SEQUENCE:").append(getSequenceNumber());
sb.append(" SEQUENCE:").append(getSequence());
sb.append(" BLOCKS TO FOLLOW:").append(getBlockCount());
sb.append(" FORMAT:").append(getFormat());
sb.append(" UNK:").append(Integer.toHexString(getInt(UNKNOWN)).toUpperCase());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@@ -111,7 +110,7 @@ public class LCMotorolaRadioReprogramHeader extends LinkControlWord
/**
* Talkgroup
*/
public Identifier getTalkgroup()
public APCO25Talkgroup getTalkgroup()
{
if(mTalkgroup == null)
{
@@ -122,20 +121,36 @@ public class LCMotorolaRadioReprogramHeader extends LinkControlWord
}
/**
* Count of continuation messages to follow
* @return
* Alias encoding format
*/
public int getRecordCount()
public String getFormat()
{
return getInt(RECORD_COUNT);
int format = getInt(FORMAT);
if(format == 1)
{
return "1-UNICODE"; //best guess
}
else
{
return String.valueOf(format);
}
}
/**
* Sequence number
* Sequence number that ties the header to each of the data blocks.
*/
public int getSequenceNumber()
public int getSequence()
{
return getInt(SEQUENCE_NUMBER);
return getInt(SEQUENCE);
}
/**
* Number of data blocks that follow this header.
*/
public int getBlockCount()
{
return getInt(BLOCK_COUNT);
}
/**

View File

@@ -0,0 +1,201 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.bits.IntField;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.alias.P25TalkerAliasIdentifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.TimeslotMessage;
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.protocol.Protocol;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola completely assembled talker alias. Note: this is not a true link control word. It is reassembled from a
* header and data blocks.
*/
public class MotorolaTalkerAliasComplete extends TimeslotMessage implements IMessage
{
private static final IntField SUID_WACN = IntField.length20(OCTET_0_BIT_0);
private static final IntField SUID_SYSTEM = IntField.length12(OCTET_2_BIT_16 + 4);
private static final IntField SUID_ID = IntField.length24(OCTET_4_BIT_32);
private static final IntField CHUNK = IntField.length16(0);
private static final int ENCODED_ALIAS_START = OCTET_7_BIT_56;
private static final int CHUNK_SIZE = 16;
private APCO25Talkgroup mTalkgroup;
private APCO25FullyQualifiedRadioIdentifier mRadio;
private P25TalkerAliasIdentifier mAlias;
private List<Identifier> mIdentifiers;
private int mSequence;
private Protocol mProtocol;
/**
* Constructs a Link Control Word from the binary message sequence.
*
* @param message assembled from the data blocks
* @param talkgroup from the header
* @param dataBlockCount from the header
* @param sequence number for the alias
* @param timeslot for the message
* @param timestamp of the most recent header or data block
* @param protocol for the message
*/
public MotorolaTalkerAliasComplete(CorrectedBinaryMessage message, APCO25Talkgroup talkgroup, int sequence,
int timeslot, long timestamp, Protocol protocol)
{
super(message, timeslot, timestamp);
mTalkgroup = talkgroup;
mSequence = sequence;
mProtocol = protocol;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
if(getTimeslot() > TIMESLOT_0)
{
sb.append("TS").append(getTimeslot()).append(" ");
}
sb.append("MOTOROLA TALKER ALIAS COMPLETE");
sb.append(" RADIO:").append(getRadio());
sb.append(" TG:").append(getTalkgroup());
sb.append(" ENCODED:").append(getEncodedAlias().toHexString());
sb.append(" ALIAS:").append(getAlias());
sb.append(" SEQUENCE:").append(mSequence);
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Protocol - P25 Phase 1 or Phase 2
*/
@Override
public Protocol getProtocol()
{
return mProtocol;
}
/**
* Sequence number for the alias.
*/
public int getSequence()
{
return mSequence;
}
/**
* Decoded alias string
*/
public P25TalkerAliasIdentifier getAlias()
{
if(mAlias == null)
{
//BinaryMessage encoded = getEncodedAlias();
//TODO: implement decoding of the encoded alias.
String alias = "*ALIAS " + getRadio().getValue() + "*"; //Temporary value until we implement decoding
mAlias = P25TalkerAliasIdentifier.create(alias);
}
return mAlias;
}
/**
* Calculates the 16-bit value at the specified chunk number.
* @param chunk to get
* @return int value at the chunk
*/
private int getChunkValue(int chunk)
{
int lastIndex = ENCODED_ALIAS_START + (chunk * CHUNK_SIZE) - 1;
if(getMessage().size() >= lastIndex)
{
return getInt(CHUNK, ENCODED_ALIAS_START + ((chunk - 1) * CHUNK_SIZE));
}
return 0;
}
/**
* Extracts the encoded alias payload.
* @return encoded alias binary message
*/
private BinaryMessage getEncodedAlias()
{
int length = 1;
for(int x = 16; x > 1; x--)
{
if(getChunkValue(x) > 0)
{
length = x;
break;
}
}
return getMessage().getSubMessage(ENCODED_ALIAS_START, ENCODED_ALIAS_START + (length * 16));
}
/**
* Talkgroup
*/
public APCO25Talkgroup getTalkgroup()
{
return mTalkgroup;
}
/**
* Radio that is being aliased.
*/
public APCO25FullyQualifiedRadioIdentifier getRadio()
{
if(mRadio == null)
{
int wacn = getInt(SUID_WACN);
int system = getInt(SUID_SYSTEM);
int id = getInt(SUID_ID);
mRadio = APCO25FullyQualifiedRadioIdentifier.createFrom(id, wacn, system, id);
}
return mRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
//TODO: uncomment once alias decoding is implemented.
// mIdentifiers.add(getAlias());
}
return mIdentifiers;
}
}

View File

@@ -33,10 +33,12 @@ import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierUpdateListener;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.alias.P25TalkerAliasIdentifier;
import io.github.dsheirer.identifier.encryption.EncryptionKey;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroupManager;
import io.github.dsheirer.identifier.patch.PatchGroupPreLoadDataContent;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.log.LoggingSuppressor;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
@@ -49,6 +51,7 @@ import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager;
import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.MotorolaTalkerAliasComplete;
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;
@@ -252,41 +255,18 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
continueState(State.CALL);
}
}
else if(message instanceof MotorolaTalkerAliasComplete tac)
{
//Debug
// if(!mTrafficChannelManager.getTalkerAliasManager().hasAlias(tac.getRadio()))
// {
// System.out.println(tac);
// }
mTrafficChannelManager.getTalkerAliasManager().update(tac.getRadio(), tac.getAlias());
}
}
}
/**
* Updates the channel state according to the PDU type
*/
private static State getStateFromPduType(MacPduType macPduType)
{
switch(macPduType)
{
case MAC_0_SIGNAL:
return State.CONTROL;
case MAC_1_PTT:
return State.CALL;
case MAC_2_END_PTT:
return State.TEARDOWN;
case MAC_4_ACTIVE:
case MAC_6_HANGTIME:
case MAC_3_IDLE:
default:
return State.ACTIVE;
}
}
/**
* Adds the current channel to the local identifier collection which will cause it to be broadcast to all of the
* other listeners and will allow both timeslots on this channel to receive it and update accordingly.
*
* @param channel to broadcast
*/
private void broadcastCurrentChannel(APCO25Channel channel)
{
getIdentifierCollection().update(channel);
}
/**
* Process MAC message structures
*/
@@ -607,10 +587,10 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
case MOTOROLA_89_GROUP_REGROUP_DELETE:
processDynamicRegrouping(message, mac);
break;
case MOTOROLA_91_GROUP_REGROUP_UNKNOWN:
case MOTOROLA_91_TALKER_ALIAS_HEADER:
//Unknown
break;
case MOTOROLA_95_UNKNOWN_149:
case MOTOROLA_95_TALKER_ALIAS_DATA_BLOCK:
//Unknown
break;
case MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED:
@@ -689,15 +669,16 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
*/
private MutableIdentifierCollection getIdentifierCollectionForUsers(List<Identifier> identifiersToAdd, long timestamp)
{
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
ic.remove(IdentifierClass.USER);
ic.remove(Form.CHANNEL);
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
mic.remove(IdentifierClass.USER);
mic.remove(Form.CHANNEL);
for(Identifier identifier : identifiersToAdd)
{
//Filter the identifiers through the patch group manager
ic.update(mPatchGroupManager.update(identifier, timestamp));
mic.update(mPatchGroupManager.update(identifier, timestamp));
}
return ic;
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(mic);
return mic;
}
/**
@@ -828,6 +809,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
*/
private void processChannelGrant(MacMessage message, MacStructure mac)
{
//TODO: will we ever see a channel grant on a non-control channel?
if(message.getMacPduType() == MacPduType.MAC_3_IDLE || message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
{
for(Identifier identifier : mac.getIdentifiers())
@@ -836,6 +818,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
getIdentifierCollection().update(mPatchGroupManager.update(identifier, message.getTimestamp()));
}
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(getIdentifierCollection());
continueState(State.ACTIVE);
}
@@ -1152,24 +1136,20 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
*/
private void processChannelUser(MacMessage message, MacStructure mac)
{
for(Identifier identifier : mac.getIdentifiers())
{
//Add to the identifier collection after filtering through the patch group manager
getIdentifierCollection().update(mPatchGroupManager.update(identifier, message.getTimestamp()));
}
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(getIdentifierCollection());
if(message.getMacPduType() == MacPduType.MAC_3_IDLE || message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
{
for(Identifier identifier : mac.getIdentifiers())
{
//Add to the identifier collection after filtering through the patch group manager
getIdentifierCollection().update(mPatchGroupManager.update(identifier, message.getTimestamp()));
}
continueState(State.ACTIVE);
}
else
{
for(Identifier identifier : mac.getIdentifiers())
{
//Add to the identifier collection after filtering through the patch group manager
getIdentifierCollection().update(mPatchGroupManager.update(identifier, message.getTimestamp()));
}
if(mac instanceof IServiceOptionsProvider sop)
{
IChannelDescriptor currentChannel = mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), getCurrentChannel(), sop.getServiceOptions(), mac.getOpcode(), getIdentifierCollection().copyOf(), message.getTimestamp(), null);
@@ -1210,12 +1190,14 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
getIdentifierCollection().update(mPatchGroupManager.update(identifier, message.getTimestamp()));
}
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(getIdentifierCollection());
if(mac instanceof PushToTalk ptt)
{
VoiceServiceOptions vso = ptt.isEncrypted() ? VoiceServiceOptions.createEncrypted() : VoiceServiceOptions.createUnencrypted();
mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), getCurrentChannel(), vso, mac.getOpcode(), getIdentifierCollection().copyOf(), message.getTimestamp(), ptt.isEncrypted() ? ptt.getEncryptionKey().toString() : null);
mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), getCurrentChannel(), vso,
mac.getOpcode(), getIdentifierCollection().copyOf(), message.getTimestamp(),
ptt.isEncrypted() ? ptt.getEncryptionKey().toString() : null);
broadcast(new DecoderStateEvent(this, Event.START, ptt.isEncrypted() ? State.ENCRYPTED : State.CALL, getTimeslot()));
}
}
@@ -1762,9 +1744,17 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
{
if(mac instanceof L3HarrisTalkerAlias talkerAlias)
{
Identifier alias = talkerAlias.getAlias();
P25TalkerAliasIdentifier alias = talkerAlias.getAlias();
getIdentifierCollection().update(alias);
mTrafficChannelManager.processP2CurrentUser(getCurrentFrequency(), getTimeslot(), alias, message.getTimestamp());
//Add the alias to the talker alias manager if we know the associated radio
Identifier from = getIdentifierCollection().getFromIdentifier();
if(from instanceof RadioIdentifier ri)
{
mTrafficChannelManager.getTalkerAliasManager().update(ri, alias);
}
}
}
@@ -1788,9 +1778,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
}
}
/**
* Creates and broadcasts a decode event.
* Creates and broadcasts a decode event and broadcasts it for the specified channel
*
* @param message for the event
* @param mac for the event
@@ -1798,13 +1787,13 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
* @param eventType of event
* @param details to populate for the event
*/
private void broadcast(MacMessage message, MacStructure mac, IChannelDescriptor channel, DecodeEventType eventType, String details)
private void broadcast(MacMessage message, MacStructure mac, IChannelDescriptor channel, DecodeEventType eventType,
String details)
{
MutableIdentifierCollection collection = getUpdatedMutableIdentifierCollection(mac);
MutableIdentifierCollection mic = getUpdatedMutableIdentifierCollection(mac);
broadcast(P25DecodeEvent.builder(eventType, message.getTimestamp()).channel(channel)
.details(details)
.identifiers(collection)
.identifiers(mic)
.timeslot(getTimeslot())
.build());
}
@@ -1819,14 +1808,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
*/
private void broadcast(MacMessage message, MacStructure structure, DecodeEventType eventType, String details)
{
MutableIdentifierCollection icQueuedResponse = getUpdatedMutableIdentifierCollection(structure);
broadcast(P25DecodeEvent.builder(eventType, message.getTimestamp())
.channel(getCurrentChannel())
.details(details)
.identifiers(icQueuedResponse)
.timeslot(getTimeslot())
.build());
broadcast(message, structure, getCurrentChannel(), eventType, details);
}
/**
@@ -1838,10 +1820,11 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
*/
private MutableIdentifierCollection getUpdatedMutableIdentifierCollection(MacStructure mac)
{
MutableIdentifierCollection icQueuedResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
icQueuedResponse.remove(IdentifierClass.USER);
icQueuedResponse.update(mac.getIdentifiers());
return icQueuedResponse;
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
mic.remove(IdentifierClass.USER);
mic.update(mac.getIdentifiers());
mTrafficChannelManager.getTalkerAliasManager().enrichMutable(mic);
return mic;
}
/**
@@ -1926,6 +1909,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
sb.append(mNetworkConfigurationMonitor.getActivitySummary());
sb.append("\n");
sb.append(mPatchGroupManager.getPatchGroupSummary());
sb.append("\n");
sb.append(mTrafficChannelManager.getTalkerAliasManager().getAliasSummary());
return sb.toString();
}

View File

@@ -31,17 +31,19 @@ import io.github.dsheirer.module.decode.p25.phase2.message.SuperFrameFragment;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureMultiFragment;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MultiFragmentContinuationMessage;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaTalkerAliasAssembler;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaTalkerAliasDataBlock;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaTalkerAliasHeader;
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractSignalingTimeslot;
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot;
import io.github.dsheirer.module.decode.p25.phase2.timeslot.Timeslot;
import io.github.dsheirer.module.decode.p25.phase2.timeslot.Voice2Timeslot;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class P25P2MessageProcessor implements Listener<IMessage>
{
@@ -52,6 +54,8 @@ public class P25P2MessageProcessor implements Listener<IMessage>
private MacMessage mMacMessageWithMultiFragment1;
private MacStructureMultiFragment mMacStructureMultiFragment1;
private MacStructureMultiFragment mMacStructureMultiFragment2;
private MotorolaTalkerAliasAssembler mMotorolaTalkerAliasAssembler1 = new MotorolaTalkerAliasAssembler(P25P2Message.TIMESLOT_1);
private MotorolaTalkerAliasAssembler mMotorolaTalkerAliasAssembler2 = new MotorolaTalkerAliasAssembler(P25P2Message.TIMESLOT_2);
private Listener<IMessage> mMessageListener;
//Map of up to 16 band identifiers per RFSS. These identifier update messages are inserted into any message that
@@ -217,14 +221,33 @@ public class P25P2MessageProcessor implements Listener<IMessage>
}
}
/* Store band identifiers so that they can be injected into channel
* type messages */
//Store band identifiers so that they can be injected into channel type messages
if(macMessage.getMacStructure() instanceof IFrequencyBand bandIdentifier)
{
mFrequencyBandMap.put(bandIdentifier.getIdentifier(), bandIdentifier);
}
//Send the message to the listener
mMessageListener.receive(macMessage);
/**
* We reassemble Motorola talker alias messages here so that we can send the assembled
* message to message listener, after the fragment has been sent to the listener.
*/
if(macMessage.getMacStructure() instanceof MotorolaTalkerAliasHeader ||
macMessage.getMacStructure() instanceof MotorolaTalkerAliasDataBlock)
{
if(macMessage.getTimeslot() == P25P2Message.TIMESLOT_1 &&
mMotorolaTalkerAliasAssembler1.add(macMessage.getMacStructure(), macMessage.getTimestamp()))
{
mMessageListener.receive(mMotorolaTalkerAliasAssembler1.assemble());
}
else if(macMessage.getTimeslot() == P25P2Message.TIMESLOT_2 &&
mMotorolaTalkerAliasAssembler2.add(macMessage.getMacStructure(), macMessage.getTimestamp()))
{
mMessageListener.receive(mMotorolaTalkerAliasAssembler2.assemble());
}
}
}
}
else if(timeslot instanceof AbstractVoiceTimeslot)

View File

@@ -136,12 +136,12 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorol
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantUpdate;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupDeleteCommand;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupExtendedFunctionCommand;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupTalkerAlias;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupTalkerAliasContinuation;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUpdate;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserAbbreviated;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserExtended;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaQueuedResponse;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaTalkerAliasDataBlock;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaTalkerAliasHeader;
import io.github.dsheirer.module.decode.p25.reference.Vendor;
import java.util.ArrayList;
import java.util.List;
@@ -584,10 +584,10 @@ public class MacMessageFactory
return new MotorolaGroupRegroupExtendedFunctionCommand(message, offset);
case MOTOROLA_89_GROUP_REGROUP_DELETE:
return new MotorolaGroupRegroupDeleteCommand(message, offset);
case MOTOROLA_91_GROUP_REGROUP_UNKNOWN:
return new MotorolaGroupRegroupTalkerAlias(message, offset);
case MOTOROLA_95_UNKNOWN_149:
return new MotorolaGroupRegroupTalkerAliasContinuation(message, offset);
case MOTOROLA_91_TALKER_ALIAS_HEADER:
return new MotorolaTalkerAliasHeader(message, offset);
case MOTOROLA_95_TALKER_ALIAS_DATA_BLOCK:
return new MotorolaTalkerAliasDataBlock(message, offset);
case MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED:
return new MotorolaGroupRegroupVoiceChannelUserExtended(message, offset);
case MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT:

View File

@@ -119,8 +119,8 @@ public enum MacOpcode
MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND(Vendor.MOTOROLA, 133, "MOTOROLA GROUP REGROUP", 11),
MOTOROLA_89_GROUP_REGROUP_DELETE(Vendor.MOTOROLA, 137, "MOTOROLA GROUP REGROUP DELETE", 17),
//Opcode 144 uses STANDARD vendor ID for some reason.
MOTOROLA_91_GROUP_REGROUP_UNKNOWN(Vendor.MOTOROLA, 145, "MOTOROLA GROUP REGROUP UNKNOWN", 17),
MOTOROLA_95_UNKNOWN_149(Vendor.MOTOROLA, 149, "MOTOROLA UNKNOWN 149", 17),
MOTOROLA_91_TALKER_ALIAS_HEADER(Vendor.MOTOROLA, 145, "MOTOROLA GROUP REGROUP UNKNOWN", 17),
MOTOROLA_95_TALKER_ALIAS_DATA_BLOCK(Vendor.MOTOROLA, 149, "MOTOROLA UNKNOWN 149", 17),
MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED(Vendor.MOTOROLA, 160, "MOTOROLA GROUP REGROUP VOICE CHANNEL USER EXTENDED", 16),
MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT(Vendor.MOTOROLA, 163, "MOTOROLA GROUP REGROUP CHANNEL GRANT IMPLICIT", 11),
MOTOROLA_A4_GROUP_REGROUP_CHANNEL_GRANT_EXPLICIT(Vendor.MOTOROLA, 164, "MOTOROLA GROUP REGROUP CHANNEL GRANT EXPLICIT", 13),

View File

@@ -1,108 +0,0 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.bits.IntField;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Group Regroup Talker Alias - 17-bytes long
*/
public class MotorolaGroupRegroupTalkerAlias extends MacStructureVendor
{
private static final IntField TALKGROUP = IntField.length16(24);
private static final IntField UNKNOWN = IntField.length32(40);
private static final IntField SOURCE_SUID_WACN = IntField.length20(72);
private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(92);
private static final IntField SOURCE_SUID_UNIT = IntField.length24(104);
private List<Identifier> mIdentifiers;
private TalkgroupIdentifier mTalkgroup;
private APCO25FullyQualifiedRadioIdentifier mRadio;
/**
* Constructs the message
*
* @param message containing the message bits
* @param offset into the message for this structure
*/
public MotorolaGroupRegroupTalkerAlias(CorrectedBinaryMessage message, int offset)
{
super(message, offset);
}
/**
* Textual representation of this message
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MOTOROLA GROUP REGROUP TALKER ALIAS TALKGROUP:").append(getTalkgroup());
sb.append(" SOURCE SUID RADIO:").append(getRadio());
sb.append(" UNK:").append(Integer.toHexString(getInt(UNKNOWN)).toUpperCase());
sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString());
return sb.toString();
}
/**
* Talkgroup identifier
*/
public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = APCO25Talkgroup.create(getMessage().getInt(TALKGROUP, getOffset()));
}
return mTalkgroup;
}
public APCO25FullyQualifiedRadioIdentifier getRadio()
{
if(mRadio == null)
{
int wacn = getMessage().getInt(SOURCE_SUID_WACN, getOffset());
int system = getMessage().getInt(SOURCE_SUID_SYSTEM, getOffset());
int unit = getMessage().getInt(SOURCE_SUID_UNIT, getOffset());
mRadio = APCO25FullyQualifiedRadioIdentifier.createFrom(unit, wacn, system, unit);
}
return mRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
}
return mIdentifiers;
}
}

View File

@@ -1,88 +0,0 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Unknown Opcode 149 (0x95) Talker Alias continuation message.
*
* This was observed at the end of a Group Regroup call sequence during HANGTIME. Each radio doesn't know the alias
* until it receives it once and then queues the alias for subsequent use.
*
* This opcode is observed following an Opcode 145 message and seems to be a continuation message.
*
* Examples:
* 959011 018E58B82BAB4D3B70E9A8457F9D C67
* 959011 0287000000000000000000000000 E26
*
* Example 2:
* 9190110BCD02010026BEE004A403151724FD9 Opcode 145
* 959011012436C4C022EC902A9943EDAA69E76 Opcode 149
* 95901102298FAA77683FAE0000000000000AB Opcode 149
* (immediately followed in the same call sequence by a slight variation)
* 9190110BCD02010036BEE004A403151724513 Opcode 145
* 959011013436C4C022EC902A9943EDAA699F6 Opcode 149
* 95901102398FAA77683FAE00000000000072B Opcode 149
*
* The opcode, vendor and length octets are consistent. The first octet seems to be a sequence identifier, 01, 02, etc.
* Nothing else seems to match any of the other identifiers that were active at call time.
*/
public class MotorolaGroupRegroupTalkerAliasContinuation extends MacStructureVendor
{
private List<Identifier> mIdentifiers;
/**
* Constructs the message
*
* @param message containing the message bits
* @param offset into the message for this structure
*/
public MotorolaGroupRegroupTalkerAliasContinuation(CorrectedBinaryMessage message, int offset)
{
super(message, offset);
}
/**
* Textual representation of this message
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MOTOROLA TALKER ALIAS CONTINUATION");
sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString());
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,152 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.MotorolaTalkerAliasComplete;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure;
import io.github.dsheirer.protocol.Protocol;
import java.util.HashMap;
import java.util.Map;
/**
* Reassembles a P25 Motorola talker alias from a Header and Data Blocks.
*
* This is used for traffic channels and monitors:
* - Phase 2: mac messaging
*/
public class MotorolaTalkerAliasAssembler
{
private static final int HEADER_FRAGMENT_LENGTH = 64;
private static final int DATA_BLOCK_FRAGMENT_LENGTH = 100;
private MotorolaTalkerAliasHeader mHeader;
private Map<Integer, MotorolaTalkerAliasDataBlock> mDataBlocks = new HashMap<>();
private int mSequence = -1;
private long mMostRecentTimestamp;
private int mTimeslot;
/**
* Constructs an instance
*/
public MotorolaTalkerAliasAssembler(int timeslot)
{
mTimeslot = timeslot;
}
/**
* Link control word to process.
* @param mac to add
* @return true if we can (now) assemble a complete talker alias from the header and data blocks.
*/
public boolean add(MacStructure mac, long timestamp)
{
if(mac instanceof MotorolaTalkerAliasHeader header)
{
mMostRecentTimestamp = timestamp;
if(mSequence != header.getSequence())
{
mDataBlocks.clear();
mSequence = header.getSequence();
}
mHeader = header;
}
else if(mac instanceof MotorolaTalkerAliasDataBlock block)
{
mMostRecentTimestamp = timestamp;
if(block.getSequence() != mSequence)
{
mHeader = null;
mDataBlocks.clear();
mSequence = block.getSequence();
}
mDataBlocks.put(block.getBlockNumber(), block);
}
else
{
return false; //For all lcw's that are not headers or data blocks
}
return isComplete();
}
/**
* Indicates if the assembler has a header and the correct number of data blocks to reassemble an alias.
*/
private boolean isComplete()
{
if(mHeader != null)
{
int dataBlockCount = mHeader.getBlockCount();
for(int x = 1; x <= dataBlockCount; x++)
{
if(!mDataBlocks.containsKey(x))
{
return false;
}
}
return true;
}
return false;
}
/**
* Assembles the talker alias once the header and data blocks have been collected.
*
* Note: do not invoke unless the add() methods indicate that the assembler is complete.
* @return assembled talker alias
* @throws IllegalStateException if the assembler can't assemble the alias.
*/
public MotorolaTalkerAliasComplete assemble() throws IllegalStateException
{
if(!isComplete())
{
throw new IllegalStateException("Can't assemble talker alias - missing header or data block(s)");
}
int dataBlockCount = mHeader.getBlockCount();
CorrectedBinaryMessage reassembled = new CorrectedBinaryMessage(HEADER_FRAGMENT_LENGTH +
(dataBlockCount * DATA_BLOCK_FRAGMENT_LENGTH));
int offset = 0;
reassembled.load(offset, mHeader.getFragment());
offset += HEADER_FRAGMENT_LENGTH;
for(int x = 1; x <= dataBlockCount; x++)
{
MotorolaTalkerAliasDataBlock block = mDataBlocks.get(x);
reassembled.load(offset, block.getFragment());
offset += DATA_BLOCK_FRAGMENT_LENGTH;
}
MotorolaTalkerAliasComplete complete = new MotorolaTalkerAliasComplete(reassembled, mHeader.getTalkgroup(),
mHeader.getSequence(), mTimeslot, mMostRecentTimestamp, Protocol.APCO25_PHASE2);
mHeader = null;
mDataBlocks.clear();
return complete;
}
}

View File

@@ -0,0 +1,107 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2024 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.bits.IntField;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Talker Alias continuation block Opcode 149 (0x95). This follows Motorola Talker Alias header message.
*
* Examples:
* 959011 018E58B82BAB4D3B70E9A8457F9D C67
* 959011 0287000000000000000000000000 E26
*
* The opcode, vendor and length octets are consistent. The first octet seems to be an identifier, 01, 02, etc.
* Nothing else seems to match any of the other identifiers that were active at call time.
*/
public class MotorolaTalkerAliasDataBlock extends MacStructureVendor
{
private static final IntField BLOCK_NUMBER = IntField.length8(OCTET_4_BIT_24);
private static final IntField SEQUENCE = IntField.length4(OCTET_5_BIT_32);
private static final int FRAGMENT_START = OCTET_5_BIT_32 + 4;
private static final int FRAGMENT_END = OCTET_18_BIT_136;
private List<Identifier> mIdentifiers;
/**
* Constructs the message
*
* @param message containing the message bits
* @param offset into the message for this structure
*/
public MotorolaTalkerAliasDataBlock(CorrectedBinaryMessage message, int offset)
{
super(message, offset);
}
/**
* Textual representation of this message
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MOTOROLA TALKER ALIAS DATA BLOCK:").append(getBlockNumber());
sb.append(" OF SEQUENCE:").append(getSequence());
sb.append(" FRAGMENT:").append(getFragment().toHexString());
sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString());
return sb.toString();
}
/**
* Fragment of encoded alias.
*/
public BinaryMessage getFragment()
{
return getMessage().getSubMessage(getOffset() + FRAGMENT_START, getOffset() + FRAGMENT_END);
}
/**
* Block number
*/
public int getBlockNumber()
{
return getInt(BLOCK_NUMBER);
}
/**
* Sequence number that identifies the header and data blocks as all part of the same sequence.
*/
public int getSequence()
{
return getInt(SEQUENCE);
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,158 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.bits.IntField;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Talker Alias Header - Opcode 145
*/
public class MotorolaTalkerAliasHeader extends MacStructureVendor
{
private static final IntField TALKGROUP = IntField.length16(OCTET_4_BIT_24);
private static final IntField BLOCK_COUNT = IntField.length8(OCTET_6_BIT_40);
private static final IntField FORMAT = IntField.length8(OCTET_7_BIT_48); //Value 1 observed - unicode?
private static final IntField UNKNOWN = IntField.length8(OCTET_8_BIT_56); //Always 0x00
private static final IntField SEQUENCE = IntField.length4(OCTET_9_BIT_64);
private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_10_BIT_72);
private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_12_BIT_88 + 4);
private static final IntField SOURCE_SUID_UNIT = IntField.length24(OCTET_14_BIT_104);
private static final int FRAGMENT_START = OCTET_10_BIT_72;
private static final int FRAGMENT_END = OCTET_18_BIT_136;
private List<Identifier> mIdentifiers;
private APCO25Talkgroup mTalkgroup;
private APCO25FullyQualifiedRadioIdentifier mRadio;
/**
* Constructs the message
*
* @param message containing the message bits
* @param offset into the message for this structure
*/
public MotorolaTalkerAliasHeader(CorrectedBinaryMessage message, int offset)
{
super(message, offset);
}
/**
* Textual representation of this message
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MOTOROLA TALKER ALIAS HEADER TG:").append(getTalkgroup());
sb.append(" RADIO:").append(getRadio());
sb.append(" SEQUENCE:").append(getSequence());
sb.append(" BLOCKS TO FOLLOW:").append(getBlockCount());
sb.append(" FORMAT:").append(getFormat());
sb.append(" FRAGMENT:").append(getFragment().toHexString());
sb.append(" UNK:").append(Integer.toHexString(getInt(UNKNOWN)).toUpperCase());
sb.append(" MSG:").append(getMessage().get(getOffset(), getMessage().length()).toHexString());
return sb.toString();
}
/**
* Fragment that is the start of the encoded alias.
*/
public BinaryMessage getFragment()
{
return getMessage().getSubMessage(getOffset() + FRAGMENT_START, getOffset() + FRAGMENT_END);
}
/**
* Alias encoding format
*/
public String getFormat()
{
int format = getInt(FORMAT);
if(format == 1)
{
return "1-UNICODE"; //possibly
}
else
{
return String.valueOf(format);
}
}
/**
* Sequence number that ties the header to each of the data blocks.
*/
public int getSequence()
{
return getInt(SEQUENCE);
}
/**
* Number of data blocks that follow this header.
*/
public int getBlockCount()
{
return getInt(BLOCK_COUNT);
}
/**
* Talkgroup identifier
*/
public APCO25Talkgroup getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = APCO25Talkgroup.create(getMessage().getInt(TALKGROUP, getOffset()));
}
return mTalkgroup;
}
public APCO25FullyQualifiedRadioIdentifier getRadio()
{
if(mRadio == null)
{
int wacn = getMessage().getInt(SOURCE_SUID_WACN, getOffset());
int system = getMessage().getInt(SOURCE_SUID_SYSTEM, getOffset());
int unit = getMessage().getInt(SOURCE_SUID_UNIT, getOffset());
mRadio = APCO25FullyQualifiedRadioIdentifier.createFrom(unit, wacn, system, unit);
}
return mRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
}
return mIdentifiers;
}
}

View File

@@ -1,5 +1,26 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.reference;
import java.util.EnumSet;
public enum Vendor
{
STANDARD( "STANDARD", "STANDARD", 0),
@@ -263,13 +284,21 @@ public enum Vendor
private String mLabel;
private String mDescription;
private int mValue;
private Vendor( String label, String description, int value )
/**
* Constructs an instance
* @param label for the vendor
* @param description of the vendor
* @param value for the vendor
*/
Vendor( String label, String description, int value )
{
mLabel = label;
mDescription = description;
mValue = value;
}
public static EnumSet<Vendor> LOGGABLE_VENDORS = EnumSet.of(STANDARD, MOTOROLA, HARRIS);
public String getLabel()
{
@@ -286,6 +315,14 @@ public enum Vendor
return mValue;
}
/**
* Vendors where we log when new opcodes are encountered.
*/
public boolean isLoggable()
{
return LOGGABLE_VENDORS.contains(this);
}
public static Vendor fromValue( int value )
{
if( 0 <= value && value <= 255 )