diff --git a/src/main/java/io/github/dsheirer/alias/AliasList.java b/src/main/java/io/github/dsheirer/alias/AliasList.java index fc211551..b931c628 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasList.java +++ b/src/main/java/io/github/dsheirer/alias/AliasList.java @@ -388,6 +388,8 @@ public class AliasList } break; case PATCH_GROUP: + List aliases = new ArrayList<>(); + PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier; PatchGroup patchGroup = patchGroupIdentifier.getValue(); @@ -395,8 +397,6 @@ public class AliasList if(patchGroupAliasList != null) { - List aliases = new ArrayList<>(); - Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup()); if(alias != null) @@ -404,19 +404,36 @@ public class AliasList aliases.add(alias); } - for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers()) + for(TalkgroupIdentifier patchedTalkgroup: patchGroup.getPatchedTalkgroupIdentifiers()) { - Alias patchedAlias = patchGroupAliasList.getAlias(patchedGroup); + Alias patchedTalkgroupAlias = patchGroupAliasList.getAlias(patchedTalkgroup); - if(patchedAlias != null && !aliases.contains(patchedAlias)) + if(patchedTalkgroupAlias != null && !aliases.contains(patchedTalkgroupAlias)) { - aliases.add(patchedAlias); + aliases.add(patchedTalkgroupAlias); } } - - return aliases; } - break; + + if(patchGroup.hasPatchedRadios()) + { + RadioAliasList radioAliasList = mRadioProtocolMap.get(patchGroupIdentifier.getProtocol()); + + if(radioAliasList != null) + { + for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers()) + { + Alias patchedRadioAlias = radioAliasList.getAlias(patchedRadio); + + if(patchedRadioAlias != null && !aliases.contains(patchedRadioAlias)) + { + aliases.add(patchedRadioAlias); + } + } + } + } + + return aliases; case RADIO: RadioIdentifier radio = (RadioIdentifier)identifier; @@ -483,9 +500,7 @@ public class AliasList { if(alias != null) { - List aliases = new ArrayList<>(); - aliases.add(alias); - return aliases; + return Collections.singletonList(alias); } return Collections.emptyList(); diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java index 89c7d4ba..442092bf 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/broadcastify/BroadcastifyCallBroadcaster.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -38,9 +38,6 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.util.ThreadPool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; @@ -48,12 +45,13 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; -import java.util.List; import java.util.Queue; import java.util.concurrent.CompletionException; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Audio broadcaster to push completed audio recordings to the Broadcastify call push API. @@ -475,7 +473,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** */ package io.github.dsheirer.identifier.patch; +import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; - import java.util.ArrayList; import java.util.List; import java.util.Objects; +/** + * Temporary grouping or regrouping of talkgroups or individual radios. + */ public class PatchGroup { -// private final static Logger mLog = LoggerFactory.getLogger(PatchGroup.class); - private TalkgroupIdentifier mPatchGroupIdentifier; - private List mPatchedGroupIdentifiers = new ArrayList<>(); + private List mPatchedTalkgroupIdentifiers = new ArrayList<>(); + private List mPatchedRadioIdentifiers = new ArrayList<>(); + private int mVersion; - public PatchGroup(TalkgroupIdentifier patchGroupIdentifier) + /** + * Constructs an instance + * @param patchGroupIdentifier for the patch group + * @param version for this patch group identifier. Note: if the patch group identifier is frequently reused, a + * new version number will indicate a new instance of the patch group. + */ + public PatchGroup(TalkgroupIdentifier patchGroupIdentifier, int version) { mPatchGroupIdentifier = patchGroupIdentifier; + mVersion = version; } + /** + * Constructs an instance + * @param patchGroupIdentifier that is the overall identifier for this temporary grouping. + */ + public PatchGroup(TalkgroupIdentifier patchGroupIdentifier) + { + this(patchGroupIdentifier, 0); + } + + /** + * Version number for this patch group used to differentiate frequently reused patch group identifiers. + * @return version number (aka super group sequence number). + */ + public int getVersion() + { + return mVersion; + } + + /** + * Patch group identifier. + * @return identifier + */ public TalkgroupIdentifier getPatchGroup() { return mPatchGroupIdentifier; } - public void addPatchedGroup(TalkgroupIdentifier patchedGroupIdentifier) + /** + * Indicates if this patch group contains any patched talkgroups. + * @return true if there are patched talkgroups. + */ + public boolean hasPatchedTalkgroups() { - if(!mPatchedGroupIdentifiers.contains(patchedGroupIdentifier)) + return !getPatchedTalkgroupIdentifiers().isEmpty(); + } + + /** + * Indicates if this patch group contains any patched radio identifiers. + * @return true if there are patched radios. + */ + public boolean hasPatchedRadios() + { + return !getPatchedRadioIdentifiers().isEmpty(); + } + + /** + * Adds the talkgroup identifier to this patched group. + * @param patchedGroupIdentifier to add + */ + public void addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier) + { + if(!mPatchedTalkgroupIdentifiers.contains(patchedGroupIdentifier)) { - mPatchedGroupIdentifiers.add(patchedGroupIdentifier); + mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier); } } - public void addPatchedGroups(List patchedGroupIdentifiers) + /** + * Adds a list of talkgroup identifiers to this patched group. + * @param patchedGroupIdentifiers to add + */ + public void addPatchedTalkgroups(List patchedGroupIdentifiers) { for(TalkgroupIdentifier identifier : patchedGroupIdentifiers) { - addPatchedGroup(identifier); + addPatchedTalkgroup(identifier); } } - public List getPatchedGroupIdentifiers() + /** + * List of patched group identifiers that are part of this patch group. + * @return patched talkgroups. + */ + public List getPatchedTalkgroupIdentifiers() { - return mPatchedGroupIdentifiers; + return mPatchedTalkgroupIdentifiers; + } + + /** + * Adds the radio identifier to this patched group. + * @param patchedRadioIdentifier to add + */ + public void addPatchedRadio(RadioIdentifier patchedRadioIdentifier) + { + if(!mPatchedRadioIdentifiers.contains(patchedRadioIdentifier)) + { + mPatchedRadioIdentifiers.add(patchedRadioIdentifier); + } + } + + /** + * Adds a list of radio identifiers to this patched group. + * @param patchedRadioIdentifiers to add + */ + public void addPatchedRadios(List patchedRadioIdentifiers) + { + for(RadioIdentifier identifier : patchedRadioIdentifiers) + { + addPatchedRadio(identifier); + } + } + + /** + * List of patched radio identifiers that are part of this patch group. + * @return patched radios. + */ + public List getPatchedRadioIdentifiers() + { + return mPatchedRadioIdentifiers; } @Override @@ -72,9 +164,14 @@ public class PatchGroup StringBuilder sb = new StringBuilder(); sb.append("P:").append(getPatchGroup()); - if(!getPatchedGroupIdentifiers().isEmpty()) + if(hasPatchedTalkgroups()) { - sb.append(" ").append(getPatchedGroupIdentifiers()); + sb.append(" TG").append(getPatchedTalkgroupIdentifiers()); + } + + if(hasPatchedRadios()) + { + sb.append(" RA").append(getPatchedRadioIdentifiers()); } return sb.toString(); diff --git a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java index 6b32c3fc..4b135b55 100644 --- a/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java +++ b/src/main/java/io/github/dsheirer/identifier/patch/PatchGroupManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,27 +22,23 @@ package io.github.dsheirer.identifier.patch; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.identifier.IdentifierClass; import io.github.dsheirer.identifier.Role; - +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** - * Manager for (temporary) patch groups and related identifiers. + * Manager for (temporary) patch groups aka super groups. This manager monitors patch group additions and deletions and + * maintains a map of the current state of each patch group active in the system. * - * Patch groups are primarily used on Motorola P25 systems. This manager monitors patch group additions and deletions - * and maintains a map of the current patch groups. - * - * Traffic channel calls will sometimes reference a patch group by simply using the patch group identifier. This - * manager will replace that reference with the full patch group so that the call event has the full patch group - * including all of the patched groups, which may not have been included in the patch group reference on the - * traffic channel. + * Traffic channel calls will reference a patch group by simply using the patch group identifier. This + * manager will replace that reference with the current state of the full patch group so that the call event has the + * full patch group including all patched talkgroups or individual radios, which may not have been included in the + * patch group reference on the control or traffic channel. */ public class PatchGroupManager { - // private final static Logger mLog = LoggerFactory.getLogger(PatchGroupManager.class); - private Map mPatchGroupLastUpdatedMap = new HashMap<>(); private Map mPatchGroupMap = new HashMap<>(); /** @@ -58,13 +54,13 @@ public class PatchGroupManager public void clear() { mPatchGroupMap.clear(); - mPatchGroupLastUpdatedMap.clear(); } /** * Adds the patch group identifier to the set of managed patch groups. If the patch group is already being - * managed, the new identifier is checked for additional patched talkgroups and they are added to the existing - * managed patch group. + * managed, the new patch group argument is checked for additional patched talkgroups and they are added to the + * existing managed patch group. Where the patch group version (super group sequence number) is different, the + * existing patch group is discarded and replaced with the updated version of the patch group. * * @param patchGroupIdentifier to add or update */ @@ -76,8 +72,19 @@ public class PatchGroupManager { if(mPatchGroupMap.containsKey(update.getPatchGroup().getValue())) { - PatchGroup current = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue(); - current.addPatchedGroups(update.getPatchedGroupIdentifiers()); + PatchGroup existingPatchGroup = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue(); + + //If the patch group version number is the same, this is an update + if(existingPatchGroup.getVersion() == update.getVersion()) + { + existingPatchGroup.addPatchedTalkgroups(update.getPatchedTalkgroupIdentifiers()); + existingPatchGroup.addPatchedRadios(update.getPatchedRadioIdentifiers()); + } + //Otherwise, this is a replace operation. + else + { + mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier); + } } else { @@ -109,7 +116,6 @@ public class PatchGroupManager { int id = patchGroupIdentifier.getValue().getPatchGroup().getValue(); mPatchGroupMap.remove(id); - mPatchGroupLastUpdatedMap.remove(id); } /** @@ -153,23 +159,28 @@ public class PatchGroupManager */ public Identifier update(Identifier identifier) { - if(identifier == null) - { - return identifier; - } - - if(identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO) + if(identifier != null && identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO) { switch(identifier.getForm()) { - case PATCH_GROUP: - if(identifier instanceof PatchGroupIdentifier) + case TALKGROUP: + if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier) + { + Identifier mapValue = mPatchGroupMap.get(talkgroupIdentifier.getValue()); + if (mapValue != null) + { + return mapValue; + } + } + break; + case PATCH_GROUP: + if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier) { - PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier; int patchGroupId = patchGroupIdentifier.getValue().getPatchGroup().getValue(); Identifier mapValue = mPatchGroupMap.get(patchGroupId); - if (mapValue != null) { + if (mapValue != null) + { return mapValue; } } 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 fcbeb164..4587c3b9 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 @@ -95,6 +95,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.isp.UMBTCTe import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.HarrisGroupRegroupExplicitEncryptionCommand; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaDenyResponse; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrant; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.PatchGroupVoiceChannelGrantUpdate; @@ -1222,6 +1223,21 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList mPatchGroupManager.removePatchGroups(tsbk.getIdentifiers()); break; + //L3HARRIS PATCH GROUP OPCODES + case HARRIS_OSP_GRG_EXENC_CMD: + if(tsbk instanceof HarrisGroupRegroupExplicitEncryptionCommand regroup) + { + if(regroup.getRegroupOptions().isActivate()) + { + mPatchGroupManager.addPatchGroup(regroup.getPatchGroup()); + } + else + { + mPatchGroupManager.removePatchGroup(regroup.getPatchGroup()); + } + } + break; + //STANDARD - INBOUND OPCODES case ISP_GROUP_VOICE_SERVICE_REQUEST: if(tsbk instanceof GroupVoiceServiceRequest) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java index b87a9a35..bcfb51e4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/filter/TerminatorMessageFilterSet.java @@ -42,6 +42,7 @@ public class TerminatorMessageFilterSet extends FilterSet addFilter(new TerminatorMessageFilter("Command/Status", LinkControlOpcode.COMMAND_STATUS_OPCODES)); addFilter(new TerminatorMessageFilter("Network/Channel", LinkControlOpcode.NETWORK_OPCODES)); addFilter(new TerminatorMessageFilter("Voice", LinkControlOpcode.VOICE_OPCODES)); + addFilter(new TerminatorMessageFilter("L3Harris", LinkControlOpcode.L3HARRIS_OPCODES)); addFilter(new TerminatorMessageFilter("Motorola", LinkControlOpcode.MOTOROLA_OPCODES)); List others = new ArrayList<>(); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java index b3b06a74..3a30f833 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/LinkControlOpcode.java @@ -98,7 +98,12 @@ public enum LinkControlOpcode MOTOROLA_PATCH_GROUP_DELETE("PATCH GROUP DELETE", 4), MOTOROLA_UNIT_GPS("UNIT GPS", 6), MOTOROLA_TALK_COMPLETE("TALK_COMPLETE", 15), - MOTOROLA_UNKNOWN("UNKNOWN", -1), + MOTOROLA_UNKNOWN("MOTOROLA UNKNOWN", -1), + + L3HARRIS_UNKNOWN_0A("UNKNOWN OPCODE 10", 10), + L3HARRIS_UNKNOWN_2A("UNKNOWN OPCODE 42", 42), + L3HARRIS_UNKNOWN_2B("UNKNOWN OPCODE 43", 43), + L3HARRIS_UNKNOWN("L3HARRIS UNKNOWN", -1), UNKNOWN("UNKNOWN", -1); @@ -130,6 +135,11 @@ public enum LinkControlOpcode public static final EnumSet MOTOROLA_OPCODES = EnumSet.range(MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER, MOTOROLA_UNKNOWN); + /** + * L3Harris Opcodes + */ + public static final EnumSet L3HARRIS_OPCODES = EnumSet.of(L3HARRIS_UNKNOWN_0A, + L3HARRIS_UNKNOWN_2A, L3HARRIS_UNKNOWN_2B, L3HARRIS_UNKNOWN); /** * Network/channel related opcodes @@ -191,6 +201,18 @@ public enum LinkControlOpcode return values()[value]; } break; + case HARRIS: + switch(value) + { + case 10: + return L3HARRIS_UNKNOWN_0A; + case 42: + return L3HARRIS_UNKNOWN_2A; + case 43: + return L3HARRIS_UNKNOWN_2B; + default: + return L3HARRIS_UNKNOWN; + } case MOTOROLA: switch(value) { @@ -209,11 +231,11 @@ public enum LinkControlOpcode default: return MOTOROLA_UNKNOWN; } - default: - if(0 <= value && value <= 63) - { - return values()[value]; - } + } + + if(0 <= value && value <= 63) + { + return values()[value]; } return UNKNOWN; 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 4b0be6fd..d127bca3 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 @@ -20,6 +20,9 @@ package io.github.dsheirer.module.decode.p25.phase1.message.lc; import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode10; +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.LCMotorolaPatchGroupAdd; import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupDelete; import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUpdate; @@ -128,6 +131,15 @@ public class LinkControlWordFactory case UNIT_TO_UNIT_VOICE_CHANNEL_USER: return new LCUnitToUnitVoiceChannelUser(binaryMessage); + case L3HARRIS_UNKNOWN_0A: + return new LCHarrisUnknownOpcode10(binaryMessage); + case L3HARRIS_UNKNOWN_2A: + return new LCHarrisUnknownOpcode42(binaryMessage); + case L3HARRIS_UNKNOWN_2B: + return new LCHarrisUnknownOpcode43(binaryMessage); + case L3HARRIS_UNKNOWN: + return new UnknownLinkControlWord(binaryMessage); + case MOTOROLA_PATCH_GROUP_ADD: return new LCMotorolaPatchGroupAdd(binaryMessage); case MOTOROLA_PATCH_GROUP_DELETE: diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode10.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode10.java new file mode 100644 index 00000000..5c6b71d5 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode10.java @@ -0,0 +1,128 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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.l3harris; + +import io.github.dsheirer.bits.BinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord; +import java.util.ArrayList; +import java.util.List; + +/** + * L3Harris Unknown Opcode 10 (0x0A) + *

+ * Observed in a traffic channel carried by a TDU during an SNDCP packet data session. The controller send an 'All + * blocks received' PDU to the radio in the middle of a stream of these TDU/LC messages and the traffic channel ended + * with Call Termination TDULC messages. So, this may be some form of current data session user information update. + */ +public class LCHarrisUnknownOpcode10 extends LinkControlWord +{ + private static final int[] UNKNOWN_1 = {16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] TARGET_RADIO = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47}; + private static final int[] SOURCE_RADIO = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71}; + + private RadioIdentifier mSourceRadio; + private RadioIdentifier mTargetRadio; + private List mIdentifiers; + + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCHarrisUnknownOpcode10(BinaryMessage 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 UNKNOWN OPCODE 10 "); + sb.append(" FM:").append(getSourceRadio()); + sb.append(" TO:").append(getTargetRadio()); + sb.append(" UNK:").append(getUnknown()); + sb.append(" MSG:").append(getMessage().toHexString()); + } + return sb.toString(); + } + + public String getUnknown() + { + return getMessage().getHex(UNKNOWN_1, 2); + } + + /** + * Source radio identifier + */ + public RadioIdentifier getSourceRadio() + { + if(mSourceRadio == null) + { + mSourceRadio = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_RADIO)); + } + + return mSourceRadio; + } + + /** + * Target radio identifier + */ + public RadioIdentifier getTargetRadio() + { + if(mTargetRadio == null) + { + mTargetRadio = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_RADIO)); + } + + return mTargetRadio; + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getSourceRadio()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode42.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode42.java new file mode 100644 index 00000000..7d9c9319 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode42.java @@ -0,0 +1,74 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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.l3harris; + +import io.github.dsheirer.bits.BinaryMessage; +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; + +/** + * L3Harris Unknown Opcode 42 (0x2A) + *

+ * Observed in a voice traffic channel carried by an LDU1. This was transmitted once followed by an Opcode 43 (0x2B) + * which is also unknown. + */ +public class LCHarrisUnknownOpcode42 extends LinkControlWord +{ + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCHarrisUnknownOpcode42(BinaryMessage 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 UNKNOWN OPCODE 42 MSG:").append(getMessage().toHexString()); + } + return sb.toString(); + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode43.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode43.java new file mode 100644 index 00000000..9703ccfa --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/l3harris/LCHarrisUnknownOpcode43.java @@ -0,0 +1,74 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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.l3harris; + +import io.github.dsheirer.bits.BinaryMessage; +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; + +/** + * L3Harris Unknown Opcode 43 (0x2B) + *

+ * Observed in a voice traffic channel carried by an LDU1. This was transmitted once following an Opcode 42 (0x2A) + * which is also unknown. + */ +public class LCHarrisUnknownOpcode43 extends LinkControlWord +{ + /** + * Constructs a Link Control Word from the binary message sequence. + * + * @param message + */ + public LCHarrisUnknownOpcode43(BinaryMessage 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 UNKNOWN OPCODE 43 MSG:").append(getMessage().toHexString()); + } + return sb.toString(); + } + + /** + * List of identifiers contained in this message + */ + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupAdd.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupAdd.java index 78b94214..628ee521 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupAdd.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupAdd.java @@ -66,7 +66,7 @@ public class LCMotorolaPatchGroupAdd extends MotorolaLinkControlWord if(mPatchGroup == null) { PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getMessage().getInt(PATCH_GROUP))); - patchGroup.addPatchedGroups(getPatchedGroups()); + patchGroup.addPatchedTalkgroups(getPatchedGroups()); mPatchGroup = APCO25PatchGroup.create(patchGroup); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupDelete.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupDelete.java index 4e635808..341391d8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupDelete.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/lc/motorola/LCMotorolaPatchGroupDelete.java @@ -66,7 +66,7 @@ public class LCMotorolaPatchGroupDelete extends MotorolaLinkControlWord if(mPatchGroup == null) { PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getMessage().getInt(PATCH_GROUP))); - patchGroup.addPatchedGroups(getPatchedGroups()); + patchGroup.addPatchedTalkgroups(getPatchedGroups()); mPatchGroup = APCO25PatchGroup.create(patchGroup); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/sndcp/ActivateTdsContextAccept.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/sndcp/ActivateTdsContextAccept.java index 90bfec9d..e63f5c86 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/sndcp/ActivateTdsContextAccept.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/pdu/packet/sndcp/ActivateTdsContextAccept.java @@ -1,7 +1,6 @@ /* - * ****************************************************************************** - * sdrtrunk - * Copyright (C) 2014-2019 Dennis Sheirer + * ***************************************************************************** + * Copyright (C) 2014-2023 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 @@ -15,7 +14,7 @@ * * 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.pdu.packet.sndcp; @@ -29,7 +28,6 @@ import io.github.dsheirer.module.decode.p25.reference.NetworkAddressType; import io.github.dsheirer.module.decode.p25.reference.PDUPriorityMaximumType; import io.github.dsheirer.module.decode.p25.reference.ReadyTimer; import io.github.dsheirer.module.decode.p25.reference.StandbyTimer; - import java.util.ArrayList; import java.util.List; @@ -81,7 +79,7 @@ public class ActivateTdsContextAccept extends SNDCPMessage } sb.append(" READY TIMER:").append(getReadyTimer().getSeconds()).append("s"); sb.append(" STANDBY TIMER:").append(getStandbyTimer().getSeconds()).append("s"); - sb.append(" MTU:").append(getMaximumTransmitUnit().getByteCount()).append("B"); + sb.append(" MTU:").append(getMaximumTransmitUnit().getByteCount()); sb.append(" AUTHORIZED GROUPS:").append(getDataAccessGroups()); return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java index 64fbf29b..4b742fc4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java @@ -176,11 +176,11 @@ public enum Opcode MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN(14, "CCH PLND SHUTDWN", "CONTROL CHANNEL PLANNED SHUTDOWN"), MOTOROLA_OSP_UNKNOWN(-1, "MOTOROLA OSP UNKNOWN OPCODE", "MOTOROLA OSP UNKNOWN OPCODE"), - //Vendor: motorola, Inbound Service Packet (ISP) + //Vendor: L3Harris, Inbound Service Packet (ISP) HARRIS_ISP_UNKNOWN(-1, "HARRIS ISP UNKNOWN OPCODE", "HARRIS ISP UNKNOWN OPCODE"), - //Vendor: harris, Outbound Service Packet (OSP) - HARRIS_OSP_TDMA_SYNC(48, "HARRIS TDMA SYNC", "HARRIS TDMA SYNC BROADCAST"), + //Vendor: L3Harris, Outbound Service Packet (OSP) + HARRIS_OSP_GRG_EXENC_CMD(48, "HARRIS REGROUP", "HARRIS GROUP REGROUP COMMAND WITH EXPLICIT ENCRYPTION"), HARRIS_OSP_UNKNOWN(-1, "HARRIS OSP UNKNOWN OPCODE", "HARRIS OSP UNKNOWN OPCODE"), //Vendor: unknown, Inbound Service Packet (ISP) @@ -294,7 +294,7 @@ public enum Opcode /** * Harris opcodes */ - public static final EnumSet HARRIS = EnumSet.of(HARRIS_ISP_UNKNOWN, HARRIS_OSP_TDMA_SYNC, + public static final EnumSet HARRIS = EnumSet.of(HARRIS_ISP_UNKNOWN, HARRIS_OSP_GRG_EXENC_CMD, HARRIS_OSP_UNKNOWN); /** @@ -425,7 +425,7 @@ public enum Opcode switch(value) { case 48: - return HARRIS_OSP_TDMA_SYNC; + return HARRIS_OSP_GRG_EXENC_CMD; default: return HARRIS_OSP_UNKNOWN; } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessageFactory.java index 9a9f7106..c6368600 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessageFactory.java @@ -27,7 +27,7 @@ import io.github.dsheirer.edac.trellis.ViterbiDecoder_1_2_P25; import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; import io.github.dsheirer.module.decode.p25.phase1.P25P1Interleave; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.isp.UnknownHarrisISPMessage; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.HarrisTDMASyncBroadcast; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.HarrisGroupRegroupExplicitEncryptionCommand; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.UnknownHarrisOSPMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.isp.UnknownMotorolaISPMessage; import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.ChannelLoading; @@ -298,8 +298,8 @@ public class TSBKMessageFactory case HARRIS_ISP_UNKNOWN: return new UnknownHarrisISPMessage(dataUnitID, message, nac, timestamp); - case HARRIS_OSP_TDMA_SYNC: - return new HarrisTDMASyncBroadcast(dataUnitID, message, nac, timestamp); + case HARRIS_OSP_GRG_EXENC_CMD: + return new HarrisGroupRegroupExplicitEncryptionCommand(dataUnitID, message, nac, timestamp); case HARRIS_OSP_UNKNOWN: return new UnknownHarrisOSPMessage(dataUnitID, message, nac, timestamp); diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/HarrisGroupRegroupExplicitEncryptionCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/HarrisGroupRegroupExplicitEncryptionCommand.java new file mode 100644 index 00000000..9ec1a268 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/HarrisGroupRegroupExplicitEncryptionCommand.java @@ -0,0 +1,200 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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.tsbk.harris.osp; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +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.P25P1DataUnitID; +import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; +import io.github.dsheirer.module.decode.p25.reference.RegroupOptions; +import java.util.ArrayList; +import java.util.List; + +/** + * Harris Group Regroup Action and Explicit Encryption Command. + *

+ * This OSP is used to create, remove or update a Supergroup address associated with one member WGID or WUID with + * explicit encryption parameters. + * + * See: https://forums.radioreference.com/threads/duke-energy-p25-system.411183/page-28#post-3908078 + */ +public class HarrisGroupRegroupExplicitEncryptionCommand extends OSPMessage +{ + private static final int[] REGROUP_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] SUPERGROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39}; + private static final int[] ENCRYPTION_KEY_ID = new int[]{40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] TARGET_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 63, 64, 65, 66, 67, 68, 69, 70, 71}; + + private TalkgroupIdentifier mSuperGroupIdentifier; + private APCO25PatchGroup mPatchGroup; + private Identifier mPatchedIdentifier; + private List mIdentifiers; + private RegroupOptions mRegroupOptions; + + /** + * Constructs a TSBK from the binary message sequence. + */ + public HarrisGroupRegroupExplicitEncryptionCommand(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) + { + super(dataUnitId, message, nac, timestamp); + } + + /** + * Options for this regrouping that indicate the regrouping actions and targets. + * @return regroup options + */ + public RegroupOptions getRegroupOptions() + { + if(mRegroupOptions == null) + { + mRegroupOptions = new RegroupOptions(getMessage().getInt(REGROUP_OPTIONS)); + } + + return mRegroupOptions; + } + + /** + * Encryption key ID to use for the patch group. Note: this only applies for patched talkgroups and the value is + * zero if the target address is a radio identifier. + * @return encryption key identifier + */ + public int getEncryptionKeyId() + { + return getMessage().getInt(ENCRYPTION_KEY_ID); + } + + /** + * Indicates if there is a specified encryption key to use for the patch or simulselect communications. + * @return true if key is specified. + */ + public boolean hasEncryptionKeyId() + { + return getEncryptionKeyId() > 0; + } + + /** + * APCO25 Patch Group or Simul-Select (e.g. Harris) loaded with the target address specified in this message. + * @return patch group + */ + public APCO25PatchGroup getPatchGroup() + { + if(mPatchGroup == null) + { + PatchGroup patchGroup = new PatchGroup(getSuperGroup(), getRegroupOptions().getSupergroupSequenceNumber()); + + if(getTargetAddress() instanceof APCO25Talkgroup talkgroup) + { + patchGroup.addPatchedTalkgroup(talkgroup); + } + else if(getTargetAddress() instanceof APCO25RadioIdentifier radio) + { + patchGroup.addPatchedRadio(radio); + } + + mPatchGroup = APCO25PatchGroup.create(patchGroup); + } + + return mPatchGroup; + } + + /** + * Target address for the regrouping action. + * @return target talkgroup or radio address. + */ + public Identifier getTargetAddress() + { + if(mPatchedIdentifier == null) + { + if(getRegroupOptions().isTalkgroupAddress()) + { + mPatchedIdentifier = APCO25Talkgroup.create(getMessage().getInt(TARGET_ADDRESS)); + } + else + { + mPatchedIdentifier = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_ADDRESS)); + } + } + + return mPatchedIdentifier; + } + + /** + * Talkgroup identifier for the super group aka patch group + * + * @return patch group identifier + */ + public TalkgroupIdentifier getSuperGroup() + { + if(mSuperGroupIdentifier == null) + { + mSuperGroupIdentifier = APCO25Talkgroup.create(getMessage().getInt(SUPERGROUP_ADDRESS)); + } + + return mSuperGroupIdentifier; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getMessageStub()); + + if(getRegroupOptions().isActivate()) + { + sb.append(" ACTIVATE"); + sb.append(getRegroupOptions().isPatch() ? " TWO-WAY PATCH" : " ONE-WAY SIMUL-SELECT"); + sb.append(" SUPERGROUP:").append(getSuperGroup()); + sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber()); + + sb.append(" INCLUDE"); + sb.append(getRegroupOptions().isTalkgroupAddress() ? " TALKGROUP:" : " RADIO:"); + sb.append(getTargetAddress()); + if(hasEncryptionKeyId()) + { + sb.append(" USE ENCRYPTION KEY:").append(getEncryptionKeyId()); + } + + } + else + { + sb.append(" DEACTIVATE SUPERGROUP:").append(getSuperGroup()); + sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber()); + } + + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/HarrisTDMASyncBroadcast.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/HarrisTDMASyncBroadcast.java deleted file mode 100644 index 68af691b..00000000 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/harris/osp/HarrisTDMASyncBroadcast.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * sdr-trunk - * 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 - * - ******************************************************************************/ - -package io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp; - -import io.github.dsheirer.bits.CorrectedBinaryMessage; -import io.github.dsheirer.identifier.Identifier; -import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID; -import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.OSPMessage; - -import java.util.Collections; -import java.util.List; - -/** - * Harris TDMA Sync Broadcast message. - * - * Note: Standard Opcode 48 is a TDMA Sync message, so this likely has something - * to do with TDMA Phase II - */ -public class HarrisTDMASyncBroadcast extends OSPMessage -{ - /** - * Constructs a TSBK from the binary message sequence. - */ - public HarrisTDMASyncBroadcast(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp) - { - super(dataUnitId, message, nac, timestamp); - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append(getMessageStub()); - sb.append(" HARRIS TDMA SYNC BROADCAST"); - sb.append(" MSG:").append(getMessage().toHexString()); - return sb.toString(); - - } - - @Override - public List getIdentifiers() - { - return Collections.EMPTY_LIST; - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupAdd.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupAdd.java index e6f9dc89..d2f077b5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupAdd.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupAdd.java @@ -66,7 +66,7 @@ public class PatchGroupAdd extends OSPMessage if(mPatchGroup == null) { PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getPatchAddress())); - patchGroup.addPatchedGroups(getPatchedTalkgroups()); + patchGroup.addPatchedTalkgroups(getPatchedTalkgroups()); mPatchGroup = APCO25PatchGroup.create(patchGroup); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupDelete.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupDelete.java index 22f693ae..06dfba20 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupDelete.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/motorola/osp/PatchGroupDelete.java @@ -66,7 +66,7 @@ public class PatchGroupDelete extends OSPMessage if(mPatchGroup == null) { PatchGroup patchGroup = new PatchGroup(APCO25Talkgroup.create(getPatchAddress())); - patchGroup.addPatchedGroups(getPatchedTalkgroups()); + patchGroup.addPatchedTalkgroups(getPatchedTalkgroups()); mPatchGroup = APCO25PatchGroup.create(patchGroup); } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java index c7e25244..12c3d09d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java @@ -87,6 +87,7 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToU import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelGrantUpdateExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserAbbreviated; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisRegroupCommand; import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -391,6 +392,19 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie case PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED: broadcast(message, mac, getCurrentChannel(), DecodeEventType.COMMAND, "UNIT REGISTRATION"); break; + case PHASE1_176_L3HARRIS_GROUP_REGROUP: + if(mac instanceof L3HarrisRegroupCommand regroup) + { + if(regroup.getRegroupOptions().isActivate()) + { + mPatchGroupManager.addPatchGroup(regroup.getPatchGroup()); + } + else + { + mPatchGroupManager.removePatchGroup(regroup.getPatchGroup()); + } + } + break; case PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED: if(mac instanceof UnitToUnitAnswerRequestExtended uuare) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java index 938f6274..bb19f9d5 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** */ package io.github.dsheirer.module.decode.p25.phase2.message.mac; @@ -87,11 +84,11 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToU import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownStructure; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnknownVendorMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisRegroupCommand; import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Message factory for creating Encoded MAC Information (EMI) message parser instances. @@ -293,6 +290,8 @@ public class MacMessageFactory return new AdjacentStatusBroadcastAbbreviated(message, offset); case PHASE1_125_IDENTIFIER_UPDATE: return new FrequencyBandUpdate(message, offset); + case PHASE1_176_L3HARRIS_GROUP_REGROUP: + return new L3HarrisRegroupCommand(message, offset); case PHASE1_192_GROUP_VOICE_CHANNEL_GRANT_EXTENDED: return new GroupVoiceChannelGrantExtended(message, offset); case PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java index d267395c..47516b45 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java @@ -76,6 +76,7 @@ public enum MacOpcode PHASE1_123_NETWORK_STATUS_BROADCAST_ABBREVIATED(123, "NETWORK STATUS BROADCAST ABBREVIATED", 11), PHASE1_124_ADJACENT_STATUS_BROADCAST_ABBREVIATED(124, "ADJACENT STATUS BROADCAST ABBREVIATED", 9), PHASE1_125_IDENTIFIER_UPDATE(125, "IDENTIFIER UPDATE", 9), + PHASE1_176_L3HARRIS_GROUP_REGROUP(176, "REGROUP COMMAND", 17), PHASE1_PARTITION_1_UNKNOWN_OPCODE(-1, "UNKNOWN PHASE 1 OPCODE", -1), VENDOR_PARTITION_2_UNKNOWN_OPCODE(-1, "UNKNOWN VENDOR OPCODE", -1), @@ -138,7 +139,7 @@ public enum MacOpcode TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED, TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER, TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER, TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE, TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED, TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED, - TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT); + TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT, PHASE1_176_L3HARRIS_GROUP_REGROUP); /** * Data channel grants diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdate.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdate.java index ba00e567..d495190a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdate.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/FrequencyBandUpdate.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** */ package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure; @@ -26,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand; import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure; - import java.util.Collections; import java.util.List; @@ -72,7 +68,7 @@ public class FrequencyBandUpdate extends MacStructure implements IFrequencyBand @Override public int getIdentifier() { - return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER); + return getMessage().getInt(FREQUENCY_BAND_IDENTIFIER, getOffset()); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisRegroupCommand.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisRegroupCommand.java new file mode 100644 index 00000000..ff470d5f --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/l3harris/L3HarrisRegroupCommand.java @@ -0,0 +1,272 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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.phase2.message.mac.structure.l3harris; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.patch.PatchGroup; +import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; +import io.github.dsheirer.module.decode.p25.identifier.patch.APCO25PatchGroup; +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.phase2.message.mac.MacStructure; +import io.github.dsheirer.module.decode.p25.reference.Encryption; +import io.github.dsheirer.module.decode.p25.reference.RegroupOptions; +import io.github.dsheirer.module.decode.p25.reference.Vendor; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * L3Harris Group Regroup Explicit Encryption Command. Provides super group activate/deactivate for dynamic regrouping + * of talkgroups or groups of individual radios and includes optional encryption algorithm and key parameters when the + * regrouping targets talkgroups (not radios). + * + * See: https://forums.radioreference.com/threads/duke-energy-p25-system.411183/page-28#post-3908078 + */ +public class L3HarrisRegroupCommand extends MacStructure +{ + private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15}; + private static final int[] LENGTH = {18, 19, 20, 21, 22, 23}; + private static final int[] REGROUP_OPTIONS = {24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] SUPERGROUP = {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + private static final int[] KEY_ID = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + + //Variation 1: Radio identifiers only. + private static final int[] RADIO_1 = {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87}; + private static final int[] RADIO_2 = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111}; + private static final int[] RADIO_3 = {112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 135}; + + //Variation 2: Talkgroups and Algorithm ID + private static final int[] ALGORITHM_ID = {64, 65, 66, 67, 68, 69, 70, 71}; + private static final int[] TALKGROUP_1 = {72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87}; + private static final int[] TALKGROUP_2 = {88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103}; + private static final int[] TALKGROUP_3 = {104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119}; + private static final int[] TALKGROUP_4 = {120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135}; + + private RegroupOptions mRegroupOptions; + private PatchGroupIdentifier mPatchGroupIdentifier; + private List mIdentifiers; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public L3HarrisRegroupCommand(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if(getVendor() == Vendor.HARRIS) + { + sb.append("L3HARRIS "); + } + else + { + sb.append("WARNING: UNKNOWN VENDOR:").append(getVendor()); + } + + if(getRegroupOptions().isActivate()) + { + sb.append(" ACTIVATE"); + sb.append(getRegroupOptions().isPatch() ? " TWO-WAY PATCH" : " ONE-WAY SIMUL-SELECT"); + sb.append(" SUPERGROUP:").append(getPatchGroup().getValue().getPatchGroup().getValue()); + sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber()); + + sb.append(" V").append(getRegroupOptions().getSupergroupSequenceNumber()); + + if(getRegroupOptions().isActivate()) + { + if(getRegroupOptions().isTalkgroupAddress()) + { + sb.append(" INCLUDE TALKGROUPS:"); + sb.append(getPatchGroup().getValue().getPatchedTalkgroupIdentifiers().stream() + .map(tg -> tg.getValue().toString()).collect(Collectors.joining(","))); + + Encryption encryption = getEncryptionAlgorithm(); + + if(encryption != Encryption.UNENCRYPTED) + { + sb.append(" USE ENCRYPTION:").append(encryption.name()); + sb.append(" KEY:").append(getKeyId()); + } + } + else + { + sb.append(" INCLUDE RADIOS:"); + sb.append(getPatchGroup().getValue().getPatchedRadioIdentifiers().stream() + .map(radio -> radio.getValue().toString()).collect(Collectors.joining(","))); + } + } + } + else + { + sb.append(" DEACTIVATE SUPERGROUP:").append(getPatchGroup().getValue().getPatchGroup().getValue()); + } + + sb.append(" MSG LENGTH:").append(getLength()); + + return sb.toString(); + } + + /** + * Vendor ID. This should be L3Harris unless another vendor is also using this Opcode. + */ + public Vendor getVendor() + { + return Vendor.fromValue(getMessage().getInt(VENDOR, getOffset())); + } + + /** + * Message length. Note: I'm unsure if this is the count of identifiers or if this is the quantity of bytes. + * @return length. + */ + public int getLength() + { + return getMessage().getInt(LENGTH, getOffset()); + } + + /** + * Regrouping options for this patch group (ie activate, deactivate, patch group version, etc. + * @return patch group options. + */ + public RegroupOptions getRegroupOptions() + { + if(mRegroupOptions == null) + { + mRegroupOptions = new RegroupOptions(getMessage().getInt(REGROUP_OPTIONS, getOffset())); + } + + return mRegroupOptions; + } + + /** + * Encryption key ID. Note: only available when the regroup options indicates this is a talkgroup supergroup. + * @return key ID. + */ + public int getKeyId() + { + return getMessage().getInt(KEY_ID, getOffset()); + } + + /** + * Encryption algorithm for talkgroup super-group. + */ + public Encryption getEncryptionAlgorithm() + { + return Encryption.fromValue(getMessage().getInt(ALGORITHM_ID, getOffset())); + } + + /** + * Supergroup/Patch group referenced by this message including any patched talkgroups or individual radio identifiers. + * @return patch group. + */ + public PatchGroupIdentifier getPatchGroup() + { + if(mPatchGroupIdentifier == null) + { + TalkgroupIdentifier patchGroupId = APCO25Talkgroup.create(getMessage().getInt(SUPERGROUP, getOffset())); + PatchGroup patchGroup = new PatchGroup(patchGroupId, getRegroupOptions().getSupergroupSequenceNumber()); + + if(getRegroupOptions().isTalkgroupAddress()) + { + int talkgroup1 = getMessage().getInt(TALKGROUP_1, getOffset()); + + if(talkgroup1 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(talkgroup1)); + + int talkgroup2 = getMessage().getInt(TALKGROUP_2, getOffset()); + + if(talkgroup2 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(talkgroup2)); + + int talkgroup3 = getMessage().getInt(TALKGROUP_3, getOffset()); + + if(talkgroup3 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(talkgroup3)); + + int talkgroup4 = getMessage().getInt(TALKGROUP_4, getOffset()); + + if(talkgroup4 > 0) + { + patchGroup.addPatchedTalkgroup(APCO25Talkgroup.create(talkgroup4)); + } + } + } + } + } + else + { + int radio1 = getMessage().getInt(RADIO_1, getOffset()); + + if(radio1 > 0) + { + patchGroup.addPatchedRadio(APCO25RadioIdentifier.createTo(radio1)); + + int radio2 = getMessage().getInt(RADIO_2, getOffset()); + + if(radio2 > 0) + { + patchGroup.addPatchedRadio(APCO25RadioIdentifier.createTo(radio2)); + + int radio3 = getMessage().getInt(RADIO_3, getOffset()); + + if(radio3 > 0) + { + patchGroup.addPatchedRadio(APCO25RadioIdentifier.createTo(radio3)); + } + } + } + } + + mPatchGroupIdentifier = APCO25PatchGroup.create(patchGroup); + } + + return mPatchGroupIdentifier; + } + + @Override + public List getIdentifiers() + { + if(mIdentifiers == null) + { + mIdentifiers = new ArrayList<>(); + mIdentifiers.add(getPatchGroup()); + } + + return mIdentifiers; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/reference/RegroupOptions.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/RegroupOptions.java new file mode 100644 index 00000000..57158f52 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/RegroupOptions.java @@ -0,0 +1,81 @@ +package io.github.dsheirer.module.decode.p25.reference; + +/** + * Harris group regroup options + */ +public class RegroupOptions +{ + private static final int MASK_REGROUP_TYPE = 0x80; + private static final int MASK_GROUP_INDIVIDUAL = 0x40; + private static final int MASK_ACTIVATE = 0x20; + private static final int MASK_SUPERGROUP_SEQUENCE_NUMBER = 0x1F; + + private int mValue; + + /** + * Constructs an instance + * @param value + */ + public RegroupOptions(int value) + { + mValue = value; + } + + /** + * Type of regrouping + * @return regroup type + */ + public RegroupType getRegroupType() + { + return (mValue & MASK_REGROUP_TYPE) == MASK_REGROUP_TYPE ? + RegroupType.SIMULSELECT_ONE_WAY_REGROUP : RegroupType.PATCH_TWO_WAY_REGROUP; + } + + /** + * Indicates if this is a two-way patch (true) or one-way simul-select (false) regrouping + * @return patch (true) or simulselect (false) + */ + public boolean isPatch() + { + return getRegroupType() == RegroupType.PATCH_TWO_WAY_REGROUP; + } + + /** + * Indicates if the regroup target address is a group address (true) or individual address (false). + * @return true for group or false for individual address. + */ + public boolean isTalkgroupAddress() + { + return (mValue & MASK_GROUP_INDIVIDUAL) == MASK_GROUP_INDIVIDUAL; + } + + /** + * Indicates if this is an activation (true) or a deactivation (false) of the supergroup. + * @return activation (true) or deactivation (false) + */ + public boolean isActivate() + { + return (mValue & MASK_ACTIVATE) == MASK_ACTIVATE; + } + + /** + * Unique identifier for the regrouping + * @return unique id SSN + */ + public int getSupergroupSequenceNumber() + { + return (mValue & MASK_SUPERGROUP_SEQUENCE_NUMBER); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(isActivate() ? "ACTIVATE" : "DEACTIVATE"); + sb.append(isTalkgroupAddress() ? " TALKGROUP" : " INDIVIDUAL"); + sb.append(isPatch() ? " PATCH" : " SIMUL-SELECT"); + return sb.toString(); + } + + public enum RegroupType {PATCH_TWO_WAY_REGROUP, SIMULSELECT_ONE_WAY_REGROUP}; +} diff --git a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java index 055929b4..95130436 100644 --- a/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java +++ b/src/main/java/io/github/dsheirer/preference/identifier/talkgroup/APCO25TalkgroupFormatter.java @@ -1,23 +1,20 @@ /* + * ***************************************************************************** + * Copyright (C) 2014-2023 Dennis Sheirer * - * * ****************************************************************************** - * * Copyright (C) 2014-2019 Dennis Sheirer - * * - * * This program is free software: you can redistribute it and/or modify - * * it under the terms of the GNU General Public License as published by - * * the Free Software Foundation, either version 3 of the License, or - * * (at your option) any later version. - * * - * * This program is distributed in the hope that it will be useful, - * * but WITHOUT ANY WARRANTY; without even the implied warranty of - * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * * GNU General Public License for more details. - * * - * * You should have received a copy of the GNU General Public License - * * along with this program. If not, see - * * ***************************************************************************** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** */ package io.github.dsheirer.preference.identifier.talkgroup; @@ -83,10 +80,20 @@ public class APCO25TalkgroupFormatter extends AbstractIntegerFormatter sb.append("["); int counter = 0; - for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers()) + for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedTalkgroupIdentifiers()) { sb.append(format(patchedGroup, format, fixedWidth)); - if(counter++ < patchGroup.getPatchedGroupIdentifiers().size() - 1) + if(counter++ < patchGroup.getPatchedTalkgroupIdentifiers().size() - 1) + { + sb.append(","); + } + } + + counter = 0; + for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers()) + { + sb.append(format(patchedRadio, format, fixedWidth)); + if(counter++ < patchGroup.getPatchedRadioIdentifiers().size() - 1) { sb.append(","); }