mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#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:
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 )
|
||||
|
||||
Reference in New Issue
Block a user