#1607 Add support for L3Harris dynamic regroup/patch messages and events. Includes parsing for L3Harris unknown opcodes 10, 42 and 43.

This commit is contained in:
sheirerd
2023-10-17 06:55:20 -04:00
committed by Dennis Sheirer
parent 123c25b142
commit 215737ad9b
28 changed files with 1186 additions and 231 deletions

View File

@@ -388,6 +388,8 @@ public class AliasList
}
break;
case PATCH_GROUP:
List<Alias> aliases = new ArrayList<>();
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
PatchGroup patchGroup = patchGroupIdentifier.getValue();
@@ -395,8 +397,6 @@ public class AliasList
if(patchGroupAliasList != null)
{
List<Alias> 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<Alias> aliases = new ArrayList<>();
aliases.add(alias);
return aliases;
return Collections.singletonList(alias);
}
return Collections.emptyList();

View File

@@ -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<Broadc
StringBuilder sb = new StringBuilder();
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(",").append(patched.getValue());
}

View File

@@ -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
@@ -20,7 +20,6 @@
package io.github.dsheirer.audio.broadcast.rdioscanner;
import com.google.common.net.HttpHeaders;
import com.google.common.base.Joiner;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
@@ -34,7 +33,6 @@ import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.MutableIdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
@@ -42,24 +40,12 @@ 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.File;
import java.nio.file.Files;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.List;
import java.util.Queue;
@@ -67,6 +53,8 @@ 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;
/**
@@ -525,7 +513,12 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
sb.append(patchGroup.getPatchGroup().getValue().toString());
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
{
sb.append(",").append(patched.getValue());
}
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
{
sb.append(",").append(patched.getValue());
}

View File

@@ -1,69 +1,161 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.identifier.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<TalkgroupIdentifier> mPatchedGroupIdentifiers = new ArrayList<>();
private List<TalkgroupIdentifier> mPatchedTalkgroupIdentifiers = new ArrayList<>();
private List<RadioIdentifier> 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<TalkgroupIdentifier> patchedGroupIdentifiers)
/**
* Adds a list of talkgroup identifiers to this patched group.
* @param patchedGroupIdentifiers to add
*/
public void addPatchedTalkgroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
{
for(TalkgroupIdentifier identifier : patchedGroupIdentifiers)
{
addPatchedGroup(identifier);
addPatchedTalkgroup(identifier);
}
}
public List<TalkgroupIdentifier> getPatchedGroupIdentifiers()
/**
* List of patched group identifiers that are part of this patch group.
* @return patched talkgroups.
*/
public List<TalkgroupIdentifier> 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<RadioIdentifier> patchedRadioIdentifiers)
{
for(RadioIdentifier identifier : patchedRadioIdentifiers)
{
addPatchedRadio(identifier);
}
}
/**
* List of patched radio identifiers that are part of this patch group.
* @return patched radios.
*/
public List<RadioIdentifier> 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();

View File

@@ -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<Integer,Long> mPatchGroupLastUpdatedMap = new HashMap<>();
private Map<Integer,PatchGroupIdentifier> 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;
}
}

View File

@@ -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)

View File

@@ -42,6 +42,7 @@ public class TerminatorMessageFilterSet extends FilterSet<IMessage>
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<LinkControlOpcode> others = new ArrayList<>();

View File

@@ -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<LinkControlOpcode> MOTOROLA_OPCODES =
EnumSet.range(MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER, MOTOROLA_UNKNOWN);
/**
* L3Harris Opcodes
*/
public static final EnumSet<LinkControlOpcode> 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;

View File

@@ -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:

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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)
* <p>
* 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<Identifier> 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<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSourceRadio());
}
return mIdentifiers;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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)
* <p>
* 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<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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)
* <p>
* 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<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
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();
}

View File

@@ -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<Opcode> HARRIS = EnumSet.of(HARRIS_ISP_UNKNOWN, HARRIS_OSP_TDMA_SYNC,
public static final EnumSet<Opcode> 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;
}

View File

@@ -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);

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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.
* <p>
* 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<Identifier> 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<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getPatchGroup());
}
return mIdentifiers;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
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<Identifier> getIdentifiers()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.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:

View File

@@ -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

View File

@@ -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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.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

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<Identifier> 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<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getPatchGroup());
}
return mIdentifiers;
}
}

View File

@@ -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};
}

View File

@@ -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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.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(",");
}