From c1e817e028e6d3e8b5fa593ece4ec8f069e1917e Mon Sep 17 00:00:00 2001 From: Dennis Sheirer Date: Sat, 5 Oct 2024 08:47:46 -0400 Subject: [PATCH] #2004 P25P1 Link Control Extended Source messages now process through decoder state correctly and display in Now Playing. --- .../APCO25FullyQualifiedRadioIdentifier.java | 13 +++ ...CO25FullyQualifiedTalkgroupIdentifier.java | 12 ++- .../decode/p25/phase1/P25P1DecoderState.java | 5 + .../p25/phase1/P25P1MessageProcessor.java | 52 +++++++---- .../lc/ExtendedSourceLinkControlWord.java | 59 +++++++++++- .../message/lc/IExtendedSourceMessage.java | 53 +++++++++++ .../message/lc/LinkControlWordFactory.java | 16 ++-- .../l3harris/HarrisTalkerAliasAssembler.java | 4 +- .../LCExtendedFunctionCommandExtended.java | 7 +- .../lc/standard/LCGroupVoiceChannelUser.java | 91 ++++++++++++++++++- .../lc/standard/LCMessageUpdateExtended.java | 7 +- .../lc/standard/LCSourceIDExtension.java | 54 ++++++++++- .../lc/standard/LCStatusUpdateExtended.java | 7 +- .../LCUnitToUnitVoiceChannelUserExtended.java | 7 +- .../p25/phase1/message/ldu/LDU1Message.java | 2 +- .../message/tdu/TDULinkControlMessage.java | 2 +- .../record/AudioRecordingManager.java | 26 ++++-- .../record/wave/AudioMetadataUtils.java | 6 +- 18 files changed, 360 insertions(+), 63 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/IExtendedSourceMessage.java diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/radio/APCO25FullyQualifiedRadioIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/radio/APCO25FullyQualifiedRadioIdentifier.java index 5c27b4fdf..fb14331e2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/radio/APCO25FullyQualifiedRadioIdentifier.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/radio/APCO25FullyQualifiedRadioIdentifier.java @@ -47,6 +47,19 @@ public Protocol getProtocol() return Protocol.APCO25; } + @Override + public String toString() + { + if(isAliased()) + { + return "ROAM " + super.toString(); + } + else + { + return "ISSI " + super.toString(); + } + } + /** * Creates a fully qualified radio and assigns the FROM role. * @param localAddress radio identifier. This can be the same as the radio ID when the fully qualified radio diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FullyQualifiedTalkgroupIdentifier.java b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FullyQualifiedTalkgroupIdentifier.java index c31a74c89..afa604493 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FullyQualifiedTalkgroupIdentifier.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/identifier/talkgroup/APCO25FullyQualifiedTalkgroupIdentifier.java @@ -20,11 +20,7 @@ package io.github.dsheirer.module.decode.p25.identifier.talkgroup; import io.github.dsheirer.identifier.Role; -import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier; -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.radio.APCO25RadioIdentifier; import io.github.dsheirer.protocol.Protocol; /** @@ -51,8 +47,14 @@ public Protocol getProtocol() return Protocol.APCO25; } + @Override + public String toString() + { + return "ISSI " + super.toString(); + } + /** - * Creates an identifier for the fully qualified talkgroup ising the FROM role. + * Creates an identifier for the fully qualified talkgroup using the FROM role. * @param groupAddress used on the local system as an alias to the fully qualified talkgroup. * @param wacn for the talkgroup home system. * @param system for the talkgroup home system. diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java index 9a792a582..8d617fd12 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java @@ -61,6 +61,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage; import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.IExtendedSourceMessage; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisReturnToControlChannel; import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasComplete; @@ -318,6 +319,10 @@ public void receive(IMessage iMessage) break; } } + else if(iMessage instanceof IExtendedSourceMessage esm && iMessage instanceof LinkControlWord lcw) + { + processLC(lcw, esm.getTimestamp(), esm.isTerminator()); + } else if(iMessage instanceof MotorolaTalkerAliasComplete tac) { mTrafficChannelManager.getTalkerAliasManager().update(tac.getRadio(), tac.getAlias()); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java index 814bf2910..9ee36a87d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1MessageProcessor.java @@ -23,9 +23,10 @@ import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver; -import io.github.dsheirer.module.decode.p25.phase1.message.lc.ExtendedSourceLinkControlWord; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.IExtendedSourceMessage; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.HarrisTalkerAliasAssembler; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisTalkerAliasBase; 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; @@ -60,7 +61,7 @@ public class P25P1MessageProcessor implements Listener /** * Temporary holding of an extended source link control message while it awaits the extension message to arrive. */ - private ExtendedSourceLinkControlWord mExtendedSourceLinkControlWord; + private IExtendedSourceMessage mExtendedSourceMessage; /** * Motorola talker alias assembler for link control header and data blocks. @@ -110,18 +111,26 @@ public void receive(IMessage message) //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()); + if(ldu.getLinkControlWord() instanceof IExtendedSourceMessage extendedSourceMessage) + { + additionalMessageToSend = reassembleLC(extendedSourceMessage); + } + else if(ldu.getLinkControlWord() instanceof LCHarrisTalkerAliasBase harrisTalkerAlias) + { + //Send the LCW to the harris talker alias assembler + additionalMessageToSend = mHarrisTalkerAliasAssembler.process(harrisTalkerAlias, ldu.getTimestamp()); + } } else if(message instanceof TDULinkControlMessage tdu) { LinkControlWord lcw = tdu.getLinkControlWord(); - reassembleLC(lcw); + if(lcw instanceof IExtendedSourceMessage extendedSourceMessage) + { + additionalMessageToSend = reassembleLC(extendedSourceMessage); + } //Motorola carries the talker alias in the TDULC - if(mMotorolaTalkerAliasAssembler.add(lcw, message.getTimestamp())) + else if(mMotorolaTalkerAliasAssembler.add(lcw, message.getTimestamp())) { additionalMessageToSend = mMotorolaTalkerAliasAssembler.assemble(); } @@ -168,7 +177,10 @@ else if(message instanceof TDULinkControlMessage tdu) if(mMessageListener != null) { - mMessageListener.receive(message); + if(message != null) + { + mMessageListener.receive(message); + } if(additionalMessageToSend != null) { @@ -179,26 +191,32 @@ else if(message instanceof TDULinkControlMessage tdu) /** * Processes link control words to reassemble source ID extension messages. - * @param linkControlWord to process + * @param lcw to process + * @return reassembled link control or null if the original lcw is a receiver (which we hold onto and send later) */ - private void reassembleLC(LinkControlWord linkControlWord) + private IMessage reassembleLC(IExtendedSourceMessage lcw) { - if(linkControlWord instanceof ExtendedSourceLinkControlWord eslcw) + IMessage toReturn = null; + + if(lcw instanceof IExtendedSourceMessage extendedSourceMessage && extendedSourceMessage.isExtensionRequired()) { - mExtendedSourceLinkControlWord = eslcw; + mExtendedSourceMessage = extendedSourceMessage; } - else if(linkControlWord instanceof LCSourceIDExtension sie && mExtendedSourceLinkControlWord != null) + else if(lcw instanceof LCSourceIDExtension sie && mExtendedSourceMessage != null) { - mExtendedSourceLinkControlWord.setSourceIDExtension(sie); - mExtendedSourceLinkControlWord = null; + mExtendedSourceMessage.setSourceIDExtension(sie); + toReturn = mExtendedSourceMessage; + mExtendedSourceMessage = null; } else { //The source extension message should always immediately follow the message that is being extended, so if //we get a message that is not an extended message or the extension, then we've missed the extension and we //should nullify any extended message that's waiting for the extension. - mExtendedSourceLinkControlWord = null; + mExtendedSourceMessage = null; } + + return toReturn; } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/ExtendedSourceLinkControlWord.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/ExtendedSourceLinkControlWord.java index c61c1b91e..24193af03 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/ExtendedSourceLinkControlWord.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/ExtendedSourceLinkControlWord.java @@ -21,28 +21,63 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.bits.IntField; +import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension; +import io.github.dsheirer.protocol.Protocol; +import java.util.List; /** * Extended link control word. Base class for adding in the extension word for messages that require two link control * fragments to fit the content. */ -public abstract class ExtendedSourceLinkControlWord extends LinkControlWord +public abstract class ExtendedSourceLinkControlWord extends LinkControlWord implements IExtendedSourceMessage { private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private LCSourceIDExtension mSourceIDExtension; private FullyQualifiedRadioIdentifier mSourceAddress; + private long mTimestamp; + private boolean mTerminator; + protected List mIdentifiers; /** * Constructs a Link Control Word from the binary message sequence. * * @param message + * @param timestamp of this message. + * @param isTerminator to indicate if message is carried by a TDULC terminator message */ - public ExtendedSourceLinkControlWord(CorrectedBinaryMessage message) + public ExtendedSourceLinkControlWord(CorrectedBinaryMessage message, long timestamp, boolean isTerminator) { super(message); + mTimestamp = timestamp; + mTerminator = isTerminator; + } + + @Override + public boolean isTerminator() + { + return mTerminator; + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + @Override + public long getTimestamp() + { + return mTimestamp; + } + + @Override + public int getTimeslot() + { + return P25P1Message.TIMESLOT_0; } /** @@ -59,6 +94,26 @@ protected LCSourceIDExtension getSourceIDExtension() public void setSourceIDExtension(LCSourceIDExtension extension) { mSourceIDExtension = extension; + mSourceAddress = null; + mIdentifiers = null; + } + + /** + * Indicates that this LCW always requires an extended source. + */ + @Override + public boolean isExtensionRequired() + { + return true; + } + + /** + * Indicates if this message has the source ID extension message appended. + */ + @Override + public boolean isFullyExtended() + { + return mSourceIDExtension != null; } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/IExtendedSourceMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/IExtendedSourceMessage.java new file mode 100644 index 000000000..3604ea27a --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/IExtendedSourceMessage.java @@ -0,0 +1,53 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2024 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.p25.phase1.message.lc; + +import io.github.dsheirer.message.IMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension; + +/** + * Interface to identify Link Control messages that can optionally be extended by appending source ID extension message. + */ +public interface IExtendedSourceMessage extends IMessage +{ + /** + * Sets or assigns the optional source ID extension message. + * @param sourceIDExtension to append. + */ + void setSourceIDExtension(LCSourceIDExtension sourceIDExtension); + + /** + * Indicates if this message requires an optional source extension message. + * @return true if required. + */ + boolean isExtensionRequired(); + + /** + * Indicates if this message requires an extended source and if that extended source is appended. + * @return true if fully extended. + */ + boolean isFullyExtended(); + + /** + * Indicates if this link control message was carried by a TDULC terminator message. + * @return true if terminator. + */ + boolean isTerminator(); +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWordFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWordFactory.java index 357a9af68..1904acfdc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWordFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlWordFactory.java @@ -81,8 +81,10 @@ public class LinkControlWordFactory * Creates a link control word from the binary message sequence. * * @param message containing the LCW binary message sequence. + * @param timestamp of the message that carries the link control word. + * @param isTerminator to indicate if message is carried by a TDULC terminator message */ - public static LinkControlWord create(CorrectedBinaryMessage message) + public static LinkControlWord create(CorrectedBinaryMessage message, long timestamp, boolean isTerminator) { LinkControlOpcode opcode = LinkControlWord.getOpcode(message); switch(opcode) @@ -104,11 +106,11 @@ public static LinkControlWord create(CorrectedBinaryMessage message) case EXTENDED_FUNCTION_COMMAND: return new LCExtendedFunctionCommand(message); case EXTENDED_FUNCTION_COMMAND_EXTENDED: - return new LCExtendedFunctionCommandExtended(message); + return new LCExtendedFunctionCommandExtended(message, timestamp, isTerminator); case GROUP_AFFILIATION_QUERY: return new LCGroupAffiliationQuery(message); case GROUP_VOICE_CHANNEL_USER: - return new LCGroupVoiceChannelUser(message); + return new LCGroupVoiceChannelUser(message, timestamp, isTerminator); case GROUP_VOICE_CHANNEL_UPDATE: return new LCGroupVoiceChannelUpdate(message); case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: @@ -116,7 +118,7 @@ public static LinkControlWord create(CorrectedBinaryMessage message) case MESSAGE_UPDATE: return new LCMessageUpdate(message); case MESSAGE_UPDATE_EXTENDED: - return new LCMessageUpdateExtended(message); + return new LCMessageUpdateExtended(message, timestamp, isTerminator); case NETWORK_STATUS_BROADCAST: return new LCNetworkStatusBroadcast(message); case NETWORK_STATUS_BROADCAST_EXPLICIT: @@ -132,13 +134,13 @@ public static LinkControlWord create(CorrectedBinaryMessage message) case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: return new LCSecondaryControlChannelBroadcastExplicit(message); case SOURCE_ID_EXTENSION: - return new LCSourceIDExtension(message); + return new LCSourceIDExtension(message, timestamp, isTerminator); case STATUS_QUERY: return new LCStatusQuery(message); case STATUS_UPDATE: return new LCStatusUpdate(message); case STATUS_UPDATE_EXTENDED: - return new LCStatusUpdateExtended(message); + return new LCStatusUpdateExtended(message, timestamp, isTerminator); case SYSTEM_SERVICE_BROADCAST: return new LCSystemServiceBroadcast(message); case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: @@ -154,7 +156,7 @@ public static LinkControlWord create(CorrectedBinaryMessage message) case UNIT_TO_UNIT_VOICE_CHANNEL_USER: return new LCUnitToUnitVoiceChannelUser(message); case UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED: - return new LCUnitToUnitVoiceChannelUserExtended(message); + return new LCUnitToUnitVoiceChannelUserExtended(message, timestamp, isTerminator); case L3HARRIS_RETURN_TO_CONTROL_CHANNEL: return new LCHarrisReturnToControlChannel(message); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/HarrisTalkerAliasAssembler.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/HarrisTalkerAliasAssembler.java index b5c4675c5..aa96f70bd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/HarrisTalkerAliasAssembler.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/HarrisTalkerAliasAssembler.java @@ -19,8 +19,6 @@ 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 */ @@ -55,7 +53,7 @@ public void reset() * @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) + public LCHarrisTalkerAliasComplete process(LCHarrisTalkerAliasBase lcw, long timestamp) { mTimestamp = timestamp; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommandExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommandExtended.java index 090808755..07be4d033 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommandExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCExtendedFunctionCommandExtended.java @@ -41,16 +41,17 @@ public class LCExtendedFunctionCommandExtended extends ExtendedSourceLinkControl private Identifier mTargetAddress; private FullyQualifiedRadioIdentifier mSourceAddress; - private List mIdentifiers; /** * Constructs a Link Control Word from the binary message sequence. * * @param message + * @param timestamp of the carrier message + * @param isTerminator to indicate if message is carried by a TDULC terminator message */ - public LCExtendedFunctionCommandExtended(CorrectedBinaryMessage message) + public LCExtendedFunctionCommandExtended(CorrectedBinaryMessage message, long timestamp, boolean isTerminator) { - super(message); + super(message, timestamp, isTerminator); } public String toString() diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUser.java index b1d107be6..796446edc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUser.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCGroupVoiceChannelUser.java @@ -22,31 +22,43 @@ 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.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.IExtendedSourceMessage; import io.github.dsheirer.module.decode.p25.phase1.message.lc.VoiceLinkControlMessage; +import io.github.dsheirer.protocol.Protocol; import java.util.ArrayList; import java.util.List; /** * Group voice channel user. */ -public class LCGroupVoiceChannelUser extends VoiceLinkControlMessage +public class LCGroupVoiceChannelUser extends VoiceLinkControlMessage implements IExtendedSourceMessage { private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_4_BIT_32); + private static final int SOURCE_ADDRESS_EXTENSION_FLAG = OCTET_3_BIT_24 + 7; private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48); private Identifier mGroupAddress; private Identifier mSourceAddress; private List mIdentifiers; + private LCSourceIDExtension mSourceIdExtension; + private long mTimestamp; + private boolean mTerminator; /** * Constructs a Link Control Word from the binary message sequence. * * @param message + * @param timestamp of the carrier message + * @param isTerminator to indicate if the carrier message is a TDULC terminator message */ - public LCGroupVoiceChannelUser(CorrectedBinaryMessage message) + public LCGroupVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, boolean isTerminator) { super(message); + mTimestamp = timestamp; + mTerminator = isTerminator; } public String toString() @@ -54,11 +66,71 @@ public String toString() StringBuilder sb = new StringBuilder(); sb.append(getMessageStub()); sb.append(" FM:").append(getSourceAddress()); + + if(isExtensionRequired() && !isFullyExtended()) + { + sb.append(" (INCOMPLETE-FULLY QUALIFIED SOURCE REQUIRED)"); + } + sb.append(" TO:").append(getGroupAddress()); sb.append(" SERVICE OPTIONS:").append(getServiceOptions()); return sb.toString(); } + @Override + public boolean isTerminator() + { + return mTerminator; + } + + @Override + public boolean isFullyExtended() + { + return isExtensionRequired() && mSourceIdExtension != null; + } + + @Override + public long getTimestamp() + { + return mTimestamp; + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + @Override + public int getTimeslot() + { + return P25P1Message.TIMESLOT_0; + } + + /** + * Indicates if this message requires an optional source ID extension. + * @return true if required. + */ + @Override + public boolean isExtensionRequired() + { + return getMessage().get(SOURCE_ADDRESS_EXTENSION_FLAG); + } + + /** + * Sets the optional source ID extension for this message. + * @param sourceIDExtension to add + */ + @Override + public void setSourceIDExtension(LCSourceIDExtension sourceIDExtension) + { + mSourceIdExtension = sourceIDExtension; + + //Nullify the source address so that it can be recreated with the full source extension values. + mSourceAddress = null; + mIdentifiers = null; + } + /** * Talkgroup address */ @@ -79,7 +151,15 @@ public Identifier getSourceAddress() { if(mSourceAddress == null) { - mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + if(isExtensionRequired() && mSourceIdExtension != null) + { + mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(getInt(SOURCE_ADDRESS), + mSourceIdExtension.getWACN(), mSourceIdExtension.getSystem(), mSourceIdExtension.getId()); + } + else + { + mSourceAddress = APCO25RadioIdentifier.createFrom(getInt(SOURCE_ADDRESS)); + } } return mSourceAddress; @@ -90,6 +170,11 @@ public Identifier getSourceAddress() */ public boolean hasSourceAddress() { + if(isExtensionRequired() && isFullyExtended()) + { + return true; + } + return getInt(SOURCE_ADDRESS) > 0; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdateExtended.java index 08499259a..f8ea758fc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdateExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCMessageUpdateExtended.java @@ -38,16 +38,17 @@ public class LCMessageUpdateExtended extends ExtendedSourceLinkControlWord private Identifier mShortDataMessage; private Identifier mTargetAddress; - private List mIdentifiers; /** * Constructs a Link Control Word from the binary message sequence. * * @param message + * @param timestamp of the carrier message + * @param isTerminator to indicate if message is carried by a TDULC terminator message */ - public LCMessageUpdateExtended(CorrectedBinaryMessage message) + public LCMessageUpdateExtended(CorrectedBinaryMessage message, long timestamp, boolean isTerminator) { - super(message); + super(message, timestamp, isTerminator); } public String toString() diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSourceIDExtension.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSourceIDExtension.java index 3784828a5..76f9c86af 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSourceIDExtension.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCSourceIDExtension.java @@ -22,7 +22,10 @@ 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.phase1.message.P25P1Message; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.IExtendedSourceMessage; import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import io.github.dsheirer.protocol.Protocol; import java.util.Collections; import java.util.List; @@ -30,20 +33,25 @@ * Source ID extension word. This is used in conjunction with another link control message to carry a fully qualified * source SUID. */ -public class LCSourceIDExtension extends LinkControlWord +public class LCSourceIDExtension extends LinkControlWord implements IExtendedSourceMessage { private static final IntField SOURCE_SUID_WACN = IntField.length20(OCTET_2_BIT_16); private static final IntField SOURCE_SUID_SYSTEM = IntField.length12(OCTET_4_BIT_32 + 4); private static final IntField SOURCE_SUID_RADIO = IntField.length24(OCTET_6_BIT_48); + private long mTimestamp; + private boolean mTerminator; /** * Constructs a Link Control Word from the binary message sequence. * * @param message + * @param isTerminator to indicate if the carrier message is a TDULC terminator message */ - public LCSourceIDExtension(CorrectedBinaryMessage message) + public LCSourceIDExtension(CorrectedBinaryMessage message, long timestamp, boolean isTerminator) { super(message); + mTimestamp = timestamp; + mTerminator = isTerminator; } @Override @@ -57,6 +65,48 @@ public String toString() return sb.toString(); } + @Override + public boolean isTerminator() + { + return mTerminator; + } + + @Override + public boolean isExtensionRequired() + { + return false; + } + + @Override + public void setSourceIDExtension(LCSourceIDExtension sourceIDExtension) + { + throw new IllegalArgumentException("Extended source cannot be attached to this message"); + } + + @Override + public boolean isFullyExtended() + { + return true; + } + + @Override + public long getTimestamp() + { + return mTimestamp; + } + + @Override + public Protocol getProtocol() + { + return Protocol.APCO25; + } + + @Override + public int getTimeslot() + { + return P25P1Message.TIMESLOT_0; + } + /** * Source SUID WACN value. */ diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdateExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdateExtended.java index 7445a7a01..c414e8564 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdateExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCStatusUpdateExtended.java @@ -41,16 +41,17 @@ public class LCStatusUpdateExtended extends ExtendedSourceLinkControlWord private Identifier mUnitStatus; private Identifier mUserStatus; private Identifier mTargetAddress; - private List mIdentifiers; /** * Constructs a Link Control Word from the binary message sequence. * * @param message + * @param timestamp of the carrier message + * @param isTerminator to indicate if message is carried by a TDULC terminator message */ - public LCStatusUpdateExtended(CorrectedBinaryMessage message) + public LCStatusUpdateExtended(CorrectedBinaryMessage message, long timestamp, boolean isTerminator) { - super(message); + super(message, timestamp, isTerminator); } public String toString() diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUserExtended.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUserExtended.java index 75671013b..43f542402 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUserExtended.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/standard/LCUnitToUnitVoiceChannelUserExtended.java @@ -39,16 +39,17 @@ public class LCUnitToUnitVoiceChannelUserExtended extends ExtendedSourceLinkCont private VoiceServiceOptions mServiceOptions; private Identifier mTargetAddress; - private List mIdentifiers; /** * Constructs a Link Control Word from the binary message sequence. * * @param message + * @param timestamp of the carrier message + * @param isTerminator to indicate if message is carried by a TDULC terminator message */ - public LCUnitToUnitVoiceChannelUserExtended(CorrectedBinaryMessage message) + public LCUnitToUnitVoiceChannelUserExtended(CorrectedBinaryMessage message, long timestamp, boolean isTerminator) { - super(message); + super(message, timestamp, isTerminator); } public String toString() diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDU1Message.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDU1Message.java index 9e7f85bbd..76735702d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDU1Message.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/ldu/LDU1Message.java @@ -195,7 +195,7 @@ private void createLinkControlWord() pointer += 6; } - mLinkControlWord = LinkControlWordFactory.create(binaryMessage); + mLinkControlWord = LinkControlWordFactory.create(binaryMessage, getTimestamp(), false); mLinkControlWord.setValid(!irrecoverableErrors); //If we corrected any bit errors, update the original message with the bit error count diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java index 84de06257..ab699fed9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tdu/TDULinkControlMessage.java @@ -160,7 +160,7 @@ private void createLinkControlWord() pointer += 6; } - mLinkControlWord = LinkControlWordFactory.create(binaryMessage); + mLinkControlWord = LinkControlWordFactory.create(binaryMessage, getTimestamp(), true); mLinkControlWord.setValid(!irrecoverableErrors); //If we corrected any bit errors, update the original message with the bit error count diff --git a/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java b/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java index 611e13d01..f85173b14 100644 --- a/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java +++ b/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java @@ -25,7 +25,6 @@ import io.github.dsheirer.identifier.IdentifierClass; import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.identifier.Role; -import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.string.StringIdentifier; import io.github.dsheirer.identifier.tone.Tone; import io.github.dsheirer.identifier.tone.ToneIdentifier; @@ -209,8 +208,7 @@ private Path getAudioRecordingPath(IdentifierCollection identifierCollection, Re if(to != null) { - String toValue = ((IntegerIdentifier)to).getValue().toString().replace(":", ""); - sb.append("_TO_").append(toValue); + sb.append("_TO_").append(clean(to.toString())); } else { @@ -218,7 +216,7 @@ private Path getAudioRecordingPath(IdentifierCollection identifierCollection, Re if(!toIdentifiers.isEmpty()) { - sb.append("_TO_").append(toIdentifiers.get(0)); + sb.append("_TO_").append(clean(toIdentifiers.get(0).toString())); } } @@ -226,8 +224,7 @@ private Path getAudioRecordingPath(IdentifierCollection identifierCollection, Re if(from != null) { - String fromValue = ((IntegerIdentifier)from).getValue().toString().replace(":", ""); - sb.append("_FROM_").append(fromValue); + sb.append("_FROM_").append(clean(from.toString())); } else { @@ -239,7 +236,7 @@ private Path getAudioRecordingPath(IdentifierCollection identifierCollection, Re { if(identifier.getForm() != Form.TONE) { - sb.append("_FROM_").append(identifier); + sb.append("_FROM_").append(clean(identifier.toString())); break; } } @@ -343,6 +340,21 @@ public void changed(ObservableValue observable, Boolean oldVa } } + public static String clean(String value) + { + if(value != null) + { + return value.replace(":", "") + .replace(".", "_") + .replace("(", "_") + .replace(")", "") + .replace("ROAM ", "") + .replace("ISSI ", ""); + } + + return null; + } + /** * Threaded queue processor to process/record each recordable audio segment */ diff --git a/src/main/java/io/github/dsheirer/record/wave/AudioMetadataUtils.java b/src/main/java/io/github/dsheirer/record/wave/AudioMetadataUtils.java index 89715ec34..3db4312ea 100644 --- a/src/main/java/io/github/dsheirer/record/wave/AudioMetadataUtils.java +++ b/src/main/java/io/github/dsheirer/record/wave/AudioMetadataUtils.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * Copyright (C) 2014-2024 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -94,7 +94,7 @@ public static Map getMetadataMap(IdentifierCollection ide if(to != null) { sb = new StringBuilder(); - sb.append(to.toString()); + sb.append(to.toString().replace("ISSI ", "")); List toAliases = aliasList.getAliases(to); @@ -110,7 +110,7 @@ public static Map getMetadataMap(IdentifierCollection ide if(from != null) { sb = new StringBuilder(); - sb.append(from.toString()); + sb.append(from.toString().replace("ISSI ", "").replace("ROAM ", "")); List fromAliases = aliasList.getAliases(from);