TALKER_ALIAS_OPCODES = EnumSet.of(FULL_STANDARD_TALKER_ALIAS_HEADER,
FULL_STANDARD_TALKER_ALIAS_BLOCK_1, FULL_STANDARD_TALKER_ALIAS_BLOCK_2, FULL_STANDARD_TALKER_ALIAS_BLOCK_3);
- private Vendor mVendor;
- private boolean mFull;
- private int mValue;
- private String mLabel;
+ private final Vendor mVendor;
+ private final boolean mFull;
+ private final int mValue;
+ private final String mLabel;
/**
* Constructs an instance
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraArc4EncryptionParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/EncryptionParameters.java
similarity index 59%
rename from src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraArc4EncryptionParameters.java
rename to src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/EncryptionParameters.java
index 3efc4ca7..86d3953b 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraArc4EncryptionParameters.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/EncryptionParameters.java
@@ -17,29 +17,30 @@
* ****************************************************************************
*/
-package io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera;
+package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
-import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
+import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
+import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
import java.util.ArrayList;
import java.util.List;
/**
- * Hytera ARC4/EP Encryption Parameters
- *
- * Note: observed as FLC payload for a PI_HEADER slot type.
- * Note: observed on a Hytera system that was configured as IP Site Connect compatible.
+ * Encryption Parameters, carried by a PI_HEADER.
+ *
+ * Note: this message repurposes the OPCODE field to carry the encryption algorithm.
*/
-public class HyteraArc4EncryptionParameters extends FullLCMessage
+public class EncryptionParameters extends FullLCMessage
{
private static final int[] KEY_ID = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] INITIALIZATION_VECTOR = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] DESTINATION_GROUP = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79};
- //Hytera version uses CRC-CCITT with 0x9696 initial fill, bits: 80-95
+ //CRC-CCITT with 0x9696 initial fill, bits: 80-95
private DMRTalkgroup mTalkgroup;
private List mIdentifiers;
@@ -50,9 +51,23 @@ public class HyteraArc4EncryptionParameters extends FullLCMessage
* @param timestamp for the message
* @param timeslot of the message
*/
- public HyteraArc4EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
+ public EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
+ int bitErrors = CRCDMR.correctCCITT80(message, 0, 80, 0x9696);
+ setValid(bitErrors < 2);
+ }
+
+ /**
+ * Overrides the default method for opcode to return a static value, because the opcode field is repurposed to
+ * be the algorithm field in both Motorola and Hytera variants of this message, both carried by the Encryption
+ * Header message.
+ * @return full encryption parameters opcode.
+ */
+ @Override
+ public LCOpcode getOpcode()
+ {
+ return LCOpcode.FULL_ENCRYPTION_PARAMETERS;
}
@Override
@@ -75,7 +90,17 @@ public class HyteraArc4EncryptionParameters extends FullLCMessage
sb.append(" *RESERVED-BIT*");
}
- sb.append("FLC HYTERA ARC4/EP ENCRYPTION PARAMETERS");
+ sb.append("FLC ENCRYPTION PARAMETERS");
+ sb.append(" VENDOR:").append(getVendor());
+ if(getAlgorithm() == EncryptionAlgorithm.UNKNOWN)
+ {
+ //List the algorithm (aka opcod) value for unknown algorithms
+ sb.append(" ALGORITHM:").append(getOpcodeValue());
+ }
+ else
+ {
+ sb.append(" ALGORITHM:").append(getAlgorithm());
+ }
sb.append(" KEY:").append(getKeyId());
sb.append(" IV:").append(getInitializationVector());
sb.append(" TALKGROUP:").append(getTalkgroup());
@@ -83,6 +108,29 @@ public class HyteraArc4EncryptionParameters extends FullLCMessage
return sb.toString();
}
+ /**
+ * Encryption parameter details
+ * @return details
+ */
+ public String getDetails()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ENCRYPTION ALGORITHM:").append(getAlgorithm());
+ sb.append(" KEY:").append(getKeyId());
+ sb.append(" IV:").append(getInitializationVector());
+ sb.append(" VENDOR:").append(getVendor());
+ return sb.toString();
+ }
+
+ /**
+ * Encryption algorithm. Note: this message repurposes the OPCODE field to carry the algorithm ID.
+ * @return algorithm or UNKNOWN.
+ */
+ public EncryptionAlgorithm getAlgorithm()
+ {
+ return EncryptionAlgorithm.fromValue(getOpcodeValue());
+ }
+
public DMRTalkgroup getTalkgroup()
{
if(mTalkgroup == null)
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FullLCMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FullLCMessage.java
index e9fb25cd..64a75eab 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FullLCMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FullLCMessage.java
@@ -30,7 +30,7 @@ public abstract class FullLCMessage extends LCMessage
{
private static final int ENCRYPTION_FLAG = 0;
private static final int RESERVED = 1;
- private static final int[] OPCODE = new int[]{2, 3, 4, 5, 6, 7};
+ protected static final int[] OPCODE = new int[]{2, 3, 4, 5, 6, 7};
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
/**
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraFullLC.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraFullLC.java
index 2c793654..571941e2 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraFullLC.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraFullLC.java
@@ -37,11 +37,12 @@ public abstract class HyteraFullLC extends FullLCMessage implements IServiceOpti
protected static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] PRIORITY_CALL_HASHED_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
- private static final int[] UNKNOWN_1 = new int[]{72, 73, 74, 75};
- private static final int[] UNKNOWN_2 = new int[]{76, 77, 78, 79};
+ //FEC: 72-96
+ //TODO: Target and Source address fields may be full 24-bit values. How do we distinguish between Hytera repeater
+ //TODO: and a Hytera XPT repeater? Do the service options identify when it's XPT?
private ServiceOptions mServiceOptions;
- private RadioIdentifier mSourceRadio;
+ protected RadioIdentifier mSourceRadio;
/**
* Constructs an instance.
@@ -110,24 +111,6 @@ public abstract class HyteraFullLC extends FullLCMessage implements IServiceOpti
return String.format("%02X", getMessage().getInt(PRIORITY_CALL_HASHED_ADDRESS)).toUpperCase();
}
- /**
- * Unknown nibble one. This value remains consistent for the FLC between the Voice Header and the Terminator
- * for calls. It does not seem to have correlation to the channel/repeater number
- */
- public int getUnknownField1()
- {
- return getMessage().getInt(UNKNOWN_1);
- }
-
- /**
- * Unknown nibble two. For each talkgroup, this field shows one value for the Voice Header and the same value but
- * bit-inverted in the Terminator.
- */
- public int getUnknownField2()
- {
- return getMessage().getInt(UNKNOWN_2);
- }
-
/**
* Source radio address
*/
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraGroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraGroupVoiceChannelUser.java
index 737dc776..406fb513 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraGroupVoiceChannelUser.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraGroupVoiceChannelUser.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2020 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
@@ -21,9 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
+import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
+import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
-
import java.util.ArrayList;
import java.util.List;
@@ -32,6 +33,11 @@ import java.util.List;
*/
public class HyteraGroupVoiceChannelUser extends HyteraFullLC
{
+ protected static final int[] TALKGROUP = new int[]{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 = 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 mTalkgroup;
private List mIdentifiers;
@@ -55,26 +61,11 @@ public class HyteraGroupVoiceChannelUser extends HyteraFullLC
sb.append("[CRC-ERROR] ");
}
- sb.append("FLC HYTERA XPT GROUP VOICE CHANNEL USER FM:");
+ sb.append("FLC HYTERA GROUP VOICE CHANNEL USER FM:");
sb.append(getSourceRadio());
sb.append(" TO:").append(getTalkgroup());
- if(isAllChannelsBusy())
- {
- sb.append(" ALL REPEATERS BUSY");
- }
- else
- {
- sb.append(" FREE REPEATER:").append(getFreeRepeater());
- }
-
- if(hasPriorityCall())
- {
- sb.append(" PRIORITY CALL FOR:").append(getPriorityCallHashedAddress());
- sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
- }
sb.append(" ").append(getServiceOptions());
- sb.append(" UNK1:").append(getUnknownField1());
- sb.append(" UNK2:").append(getUnknownField2());
+ sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@@ -85,12 +76,25 @@ public class HyteraGroupVoiceChannelUser extends HyteraFullLC
{
if(mTalkgroup == null)
{
- mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
+ mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
}
return mTalkgroup;
}
+ /**
+ * Source radio address
+ */
+ public RadioIdentifier getSourceRadio()
+ {
+ if(mSourceRadio == null)
+ {
+ mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
+ }
+
+ return mSourceRadio;
+ }
+
@Override
public List getIdentifiers()
{
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraTerminator.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraTerminator.java
index ee828325..8cb695fc 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraTerminator.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraTerminator.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2020 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
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
-
import java.util.ArrayList;
import java.util.List;
@@ -73,8 +72,6 @@ public class HyteraTerminator extends HyteraFullLC
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
}
sb.append(" ").append(getServiceOptions());
- sb.append(" UNK1:").append(getUnknownField1());
- sb.append(" UNK2:").append(getUnknownField2());
return sb.toString();
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraUnitToUnitVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraUnitToUnitVoiceChannelUser.java
index c5ebc8af..77da285e 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraUnitToUnitVoiceChannelUser.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/hytera/HyteraUnitToUnitVoiceChannelUser.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2020 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
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
-
import java.util.ArrayList;
import java.util.List;
@@ -72,8 +71,6 @@ public class HyteraUnitToUnitVoiceChannelUser extends HyteraFullLC
sb.append(" PRIORITY CALL FOR:").append(getPriorityCallHashedAddress());
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
}
- sb.append(" UNK1:").append(getUnknownField1());
- sb.append(" UNK2:").append(getUnknownField2());
sb.append(" ").append(getServiceOptions());
return sb.toString();
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaArc4EncryptionParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaArc4EncryptionParameters.java
deleted file mode 100644
index a1c0d9f1..00000000
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaArc4EncryptionParameters.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * *****************************************************************************
- * 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.dmr.message.data.lc.full.motorola;
-
-import io.github.dsheirer.bits.CorrectedBinaryMessage;
-import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
-import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Motorola ARC4/EP Encryption Parameters
- *
- * Note: observed as FLC payload for a PI_HEADER slot type.
- * Note: observed on a possible Hytera (clone) system that was configured as IP Site Connect compatible.
- */
-public class MotorolaArc4EncryptionParameters extends FullLCMessage
-{
- private static final int[] KEY_ID = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
- private static final int[] INITIALIZATION_VECTOR = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
- 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
- private static final int[] DESTINATION_GROUP = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
- //Motorola Reed Solomon FEC bits: 72-95
-
- private DMRTalkgroup mTalkgroup;
- private List mIdentifiers;
-
- /**
- * Constructor
- * @param message bits
- * @param timestamp for the message
- * @param timeslot of the message
- */
- public MotorolaArc4EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
- {
- super(message, timestamp, timeslot);
- }
-
- @Override
- public String toString()
- {
- StringBuilder sb = new StringBuilder();
-
- if(!isValid())
- {
- sb.append("[CRC-ERROR] ");
- }
-
- if(isEncrypted())
- {
- sb.append(" *ENCRYPTED*");
- }
-
- if(isReservedBitSet())
- {
- sb.append(" *RESERVED-BIT*");
- }
-
- sb.append("FLC MOTOROLA ARC4/EP ENCRYPTION PARAMETERS");
- sb.append(" KEY:").append(getKeyId());
- sb.append(" IV:").append(getInitializationVector());
- sb.append(" TALKGROUP:").append(getTalkgroup());
- sb.append(" MSG:").append(getMessage().toHexString());
- return sb.toString();
- }
-
- public DMRTalkgroup getTalkgroup()
- {
- if(mTalkgroup == null)
- {
- mTalkgroup = new DMRTalkgroup(getMessage().getInt(DESTINATION_GROUP));
- }
-
- return mTalkgroup;
- }
-
- public int getKeyId()
- {
- return getMessage().getInt(KEY_ID);
- }
-
- public String getInitializationVector()
- {
- return getMessage().getHex(INITIALIZATION_VECTOR, 8);
- }
-
- @Override
- public List getIdentifiers()
- {
- if(mIdentifiers == null)
- {
- mIdentifiers = new ArrayList<>();
- mIdentifiers.add(getTalkgroup());
- }
-
- return mIdentifiers;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequence.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequence.java
index d22bc681..b1a897c6 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequence.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequence.java
@@ -24,7 +24,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
+import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
+import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
import java.util.ArrayList;
import java.util.List;
@@ -33,12 +35,12 @@ import java.util.List;
*/
public class PacketSequence
{
- private int mTimeslot;
+ private final int mTimeslot;
private PacketSequenceHeader mPacketSequenceHeader;
private UDTHeader mUDTHeader;
private ProprietaryDataHeader mProprietaryDataHeader;
- private List mPreambles = new ArrayList<>();
- private List mDataBlocks = new ArrayList<>();
+ private final List mPreambles = new ArrayList<>();
+ private final List mDataBlocks = new ArrayList<>();
public PacketSequence(int timeslot)
{
@@ -96,7 +98,19 @@ public class PacketSequence
*/
public boolean isEncrypted()
{
- return hasProprietaryDataHeader() && getProprietaryDataHeader() instanceof MotorolaDataEncryptionHeader;
+ if(hasProprietaryDataHeader())
+ {
+ if(getProprietaryDataHeader() instanceof MotorolaDataEncryptionHeader)
+ {
+ return true;
+ }
+ else if(getProprietaryDataHeader() instanceof HyteraDataEncryptionHeader hdeh)
+ {
+ return hdeh.getAlgorithm() != EncryptionAlgorithm.NO_ENCRYPTION;
+ }
+ }
+
+ return false;
}
public int getTimeslot()
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequenceMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequenceMessageFactory.java
index 4a27855c..739b5b06 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequenceMessageFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/PacketSequenceMessageFactory.java
@@ -25,17 +25,18 @@ import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
-import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
-import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
+import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat;
+import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.ip.DefinedShortDataPacket;
import io.github.dsheirer.module.decode.ip.UnknownPacket;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenHeader;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
+import io.github.dsheirer.module.decode.ip.hytera.shortdata.HyteraShortDataPacket;
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Header;
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
@@ -167,19 +168,30 @@ public class PacketSequenceMessageFactory
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
}
- else if(secondaryHeader instanceof HyteraProprietaryDataHeader)
+ else if(secondaryHeader instanceof HyteraDataEncryptionHeader hdeh)
{
- HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
+ ServiceAccessPoint sap = hdeh.getServiceAccessPoint();
- if(hyteraTokenHeader.isSMSMessage())
+ switch(sap)
{
- return new DMRPacketMessage(packetSequence, new HyteraSmsPacket(hyteraTokenHeader), packet,
- packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
- }
- else
- {
- return new DMRPacketMessage(packetSequence, new HyteraUnknownPacket(hyteraTokenHeader), packet,
- packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
+ case SHORT_DATA:
+ return new DMRPacketMessage(packetSequence, new HyteraShortDataPacket(packetSequence, packet), packet,
+ packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
+ case PROPRIETARY_DATA:
+ HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
+ if(hyteraTokenHeader.isSMSMessage())
+ {
+ return new DMRPacketMessage(packetSequence, new HyteraSmsPacket(hyteraTokenHeader), packet,
+ packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
+ }
+ else
+ {
+ return new DMRPacketMessage(packetSequence, new HyteraUnknownPacket(hyteraTokenHeader), packet,
+ packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
+ }
+ default:
+ return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
+ packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
}
else if(secondaryHeader instanceof MotorolaDataEncryptionHeader &&
@@ -248,7 +260,7 @@ public class PacketSequenceMessageFactory
if(sequence.hasProprietaryDataHeader())
{
- CorrectedBinaryMessage prefix = ((ProprietaryDataHeader)sequence.getProprietaryDataHeader()).getPacketPrefix();
+ CorrectedBinaryMessage prefix = sequence.getProprietaryDataHeader().getPacketPrefix();
if(prefix != null)
{
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/EncryptionAlgorithm.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/EncryptionAlgorithm.java
new file mode 100644
index 00000000..a50ba71d
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/EncryptionAlgorithm.java
@@ -0,0 +1,82 @@
+/*
+ * *****************************************************************************
+ * 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.dmr.message.type;
+
+/**
+ * Enumeration of encryption algorithms
+ */
+public enum EncryptionAlgorithm
+{
+ NO_ENCRYPTION(0x00, "NO ENCRYPTION"),
+ HYTERA_BASIC_PRIVACY(0x01, "HYTERA BP"),
+ HYTERA_ENHANCED_PRIVACY(0x02, "HYTERA RC4/EP"),
+ DMRA_RC4(0x21, "DMRA RC4/EP"),
+ DMRA_AES128(0x24, "DMRA AES128"),
+ DMRA_AES256(0x25, "DMRA AES256" ),
+ HYTERA_ENHANCED_PRIVACY_2(0x26, "HYTERA RC4/EP"),
+ UNKNOWN(-1, "UNKNOWN");
+
+ private final int mValue;
+ private final String mLabel;
+
+ /**
+ * Constructor
+ * @param value for the algorithm
+ * @param label to display
+ */
+ EncryptionAlgorithm(int value, String label)
+ {
+ mValue = value;
+ mLabel = label;
+ }
+
+ /**
+ * Numeric value for the algorithm
+ * @return value.
+ */
+ public int getValue()
+ {
+ return mValue;
+ }
+
+ /**
+ * Utility method to lookup the encryption from the value.
+ * @param value to lookup
+ * @return matching entry or UNKNOWN
+ */
+ public static EncryptionAlgorithm fromValue(int value)
+ {
+ for(EncryptionAlgorithm algorithm: EncryptionAlgorithm.values())
+ {
+ if(algorithm.getValue() == value)
+ {
+ return algorithm;
+ }
+ }
+
+ return UNKNOWN;
+ }
+
+ @Override
+ public String toString()
+ {
+ return mLabel;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java
index db58f16d..c4bd455d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java
@@ -25,8 +25,8 @@ import io.github.dsheirer.edac.BPTC_16_2;
import io.github.dsheirer.edac.Golay24;
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
-import io.github.dsheirer.module.decode.dmr.message.voice.embedded.Arc4EncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
+import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NonStandardShortBurst;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NullShortBurst;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurst;
@@ -38,8 +38,8 @@ import io.github.dsheirer.module.decode.dmr.message.voice.embedded.UnknownShortB
* Monitors audio call voice frame messaging to detect encrypted audio calls and extract the encryption parameters
* that are embedded into the six voice frames that comprise a voice super-frame.
*
- * See patent: https://patents.google.com/patent/EP2347540B1/en - embedding encryption parameters in voice super frame
- * See patent: https://patents.google.com/patent/US8271009B2 - TXI - interrupting voice transmissions
+ * See patent: ... - embedding encryption parameters in voice super frame
+ * See patent: ... - TXI - interrupting voice transmissions
*/
public class VoiceSuperFrameProcessor
{
@@ -47,8 +47,6 @@ public class VoiceSuperFrameProcessor
33, 34, 35, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] CRC4 = new int[]{56, 57, 58, 59};
private boolean mEncrypted = false;
- private int mAlgorithm;
- private int mKey;
private byte[] mFragmentA;
private byte[] mFragmentB;
private byte[] mFragmentC;
@@ -83,8 +81,6 @@ public class VoiceSuperFrameProcessor
private void softReset()
{
- mAlgorithm = 0;
- mKey = 0;
mFragmentA = null;
mFragmentB = null;
mFragmentC = null;
@@ -124,9 +120,6 @@ public class VoiceSuperFrameProcessor
{
if(voiceMessage instanceof VoiceEMBMessage voiceEMB)
{
- boolean valid = voiceEMB.getEMB().isValid();
- boolean encrypted = voiceEMB.getEMB().isEncrypted();
-
if(voiceEMB.getEMB().isValid() && voiceEMB.getEMB().isEncrypted())
{
mEncrypted = true;
@@ -175,7 +168,6 @@ public class VoiceSuperFrameProcessor
* initialization vector to return a parameters object.
*
* @param frameFFragment containing the short-burst FLC fragment
- * @param iv that was previously extracted from the voice super-frame
* @return embedded parameters.
*/
private ShortBurst extractShortBurst(BinaryMessage frameFFragment)
@@ -191,7 +183,7 @@ public class VoiceSuperFrameProcessor
return switch(opcode)
{
case NULL -> new NullShortBurst(decoded);
- case ARC4_ENCRYPTION -> new Arc4EncryptionParameters(decoded);
+ case ARC4_ENCRYPTION, AES128_ENCRYPTION, AES256_ENCRYPTION -> new EncryptionParameters(decoded);
case TXI_DELAY -> new TransmitInterrupt(decoded);
default -> new UnknownShortBurst(decoded);
};
@@ -233,11 +225,11 @@ public class VoiceSuperFrameProcessor
if(passes)
{
- return String.format("%04X", iv);
+ return String.format("%08X", iv);
}
else
{
- return String.format("%04X", iv) + "(CRC-FAIL " + check1 + "/" + check2 + "/" + check3 + "/" +
+ return String.format("%08X", iv) + "(CRC-FAIL " + check1 + "/" + check2 + "/" + check3 + "/" +
reassembled.getCorrectedBitCount() + ")";
}
}
@@ -262,10 +254,10 @@ public class VoiceSuperFrameProcessor
*/
public static boolean crc4(int value, int crc)
{
- long checksum = (value & 0x0FFFFFFFFl) << 4;
+ long checksum = (value & 0x0FFFFFFFFL) << 4;
checksum ^= 0xF; //Initial fill
- long polynomial = 0x013l << 31;
- long checkBit = 0x1l << 35;
+ long polynomial = 0x013L << 31;
+ long checkBit = 0x1L << 35;
for(int x = 31; x >= 0; x--)
{
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/Arc4EncryptionParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EncryptionParameters.java
similarity index 68%
rename from src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/Arc4EncryptionParameters.java
rename to src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EncryptionParameters.java
index e4385b0c..987bc00c 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/Arc4EncryptionParameters.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EncryptionParameters.java
@@ -20,11 +20,12 @@
package io.github.dsheirer.module.decode.dmr.message.voice.embedded;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
/**
* Encryption parameters short burst payload.
*/
-public class Arc4EncryptionParameters extends ShortBurst
+public class EncryptionParameters extends ShortBurst
{
private static final int[] KEY = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
private static final int[] ALGORITHM = new int[]{8, 9, 10};
@@ -34,7 +35,7 @@ public class Arc4EncryptionParameters extends ShortBurst
*
* @param message containing the de-interleaved and error-corrected short burst payload.
*/
- public Arc4EncryptionParameters(CorrectedBinaryMessage message)
+ public EncryptionParameters(CorrectedBinaryMessage message)
{
super(message);
}
@@ -49,31 +50,45 @@ public class Arc4EncryptionParameters extends ShortBurst
}
/**
- * Encryption algorithm
- * @return algorithm (0 - 7). 1 = ARC4
+ * Encryption algorithm value
+ * @return algorithm (0 - 7).
*/
public int getAlgorithmValue()
{
return getMessage().getInt(ALGORITHM);
}
- public String getAlgorithm()
+ /**
+ * Encryption algorithm.
+ * @return algorithm
+ */
+ public EncryptionAlgorithm getAlgorithm()
{
int algorithm = getAlgorithmValue();
- if(algorithm == 1)
+ return switch(algorithm)
{
- return "EP/ARC4";
- }
-
- return "ALGORITHM:" + algorithm;
+ case 1 -> EncryptionAlgorithm.DMRA_RC4;
+ case 4 -> EncryptionAlgorithm.DMRA_AES128;
+ case 5 -> EncryptionAlgorithm.DMRA_AES256;
+ default -> EncryptionAlgorithm.UNKNOWN;
+ };
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
- sb.append("ENCRYPTION:").append(getAlgorithm());
+
+ sb.append("ENCRYPTION ALGORITHM:");
+ if(getAlgorithm() == EncryptionAlgorithm.UNKNOWN)
+ {
+ sb.append(getAlgorithmValue());
+ }
+ else
+ {
+ sb.append(getAlgorithm());
+ }
sb.append(" KEY:").append(getKey());
return sb.toString();
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/ShortBurstOpcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/ShortBurstOpcode.java
index 8f9da887..ffde9150 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/ShortBurstOpcode.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/ShortBurstOpcode.java
@@ -21,17 +21,18 @@ package io.github.dsheirer.module.decode.dmr.message.voice.embedded;
/**
* Short Burst opcode
- *
- * See: https://patents.google.com/patent/US8271009B2 Page 12
+ * See: ... Page 12
*/
public enum ShortBurstOpcode
{
NULL(0),
ARC4_ENCRYPTION(1),
TXI_DELAY(3),
+ AES128_ENCRYPTION(4),
+ AES256_ENCRYPTION(5),
UNKNOWN(-1);
- private int mValue;
+ private final int mValue;
/**
* Constructor
diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java
index 51b61733..8e0d59eb 100644
--- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java
+++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java
@@ -67,6 +67,7 @@ public enum DecodeEventType
PAGE("Page"),
QUERY("Query"),
RADIO_CHECK("Radio Check"),
+ RADIO_REGISTRATION_SERVICE("Hytera RRS"),
REGISTER("Register"),
REGISTER_ESN("ESN"),
REQUEST("Request"),
@@ -78,9 +79,10 @@ public enum DecodeEventType
STATUS("Status"),
UDP_PACKET("UDP/IP Packet"),
UNKNOWN_PACKET("Unknown Packet"),
+ XCMP("Motorola XCMP"),
UNKNOWN("Unknown");
- private String mLabel;
+ private final String mLabel;
/**
* Encrypted voice call event types for filtering
diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/hytera/shortdata/HyteraShortDataPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/hytera/shortdata/HyteraShortDataPacket.java
new file mode 100644
index 00000000..61ca16c0
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/ip/hytera/shortdata/HyteraShortDataPacket.java
@@ -0,0 +1,120 @@
+/*
+ * *****************************************************************************
+ * 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.ip.hytera.shortdata;
+
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.identifier.Identifier;
+import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
+import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequence;
+import io.github.dsheirer.module.decode.ip.IHeader;
+import io.github.dsheirer.module.decode.ip.IPacket;
+import java.util.List;
+
+/**
+ * Hytera Short Data Packet Sequence
+ */
+public class HyteraShortDataPacket implements IPacket
+{
+ private final PacketSequence mPacketSequence;
+ private CorrectedBinaryMessage mMessage;
+
+ /**
+ * Constructor
+ * @param packetSequence containing the headers and data blocks
+ * @param payload containing the reconstructed short data message from the data blocks.
+ */
+ public HyteraShortDataPacket(PacketSequence packetSequence, CorrectedBinaryMessage payload)
+ {
+ mPacketSequence = packetSequence;
+ mMessage = payload;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("CC:").append(getPacketSequence().getPacketSequenceHeader().getSlotType().getColorCode());
+ sb.append(" HYTERA SHORT DATA MESSAGE FM:").append(getPacketSequence().getPacketSequenceHeader().getSourceLLID());
+ sb.append(" TO:").append(getPacketSequence().getPacketSequenceHeader().getDestinationLLID());
+
+ if(getPacketSequence().isEncrypted())
+ {
+ HyteraDataEncryptionHeader hdeh = (HyteraDataEncryptionHeader)getPacketSequence().getProprietaryDataHeader();
+ sb.append(" ENCRYPTED ALGORITHM:").append(hdeh.getAlgorithm());
+ sb.append(" KEY:").append(hdeh.getKeyId());
+ sb.append(" IV:").append(hdeh.getIV());
+ }
+
+ sb.append(" SHORT DATA:").append(getMessage().toHexString());
+ return sb.toString();
+
+ }
+
+ @Override
+ public IHeader getHeader()
+ {
+ return null;
+ }
+
+ @Override
+ public IPacket getPayload()
+ {
+ return null;
+ }
+
+ @Override
+ public boolean hasPayload()
+ {
+ return false;
+ }
+
+ /**
+ * Payload message
+ * @return message
+ */
+ public CorrectedBinaryMessage getMessage()
+ {
+ return mMessage;
+ }
+
+ /**
+ * Captured packet sequence
+ * @return packet sequence
+ */
+ public PacketSequence getPacketSequence()
+ {
+ return mPacketSequence;
+ }
+
+ /**
+ * Indicates if this packet is encrypted, meaning it contains a Hytera Data Encryption Header.
+ * @return true if encrypted
+ */
+ public boolean isEncrypted()
+ {
+ return getPacketSequence().isEncrypted();
+ }
+
+ @Override
+ public List getIdentifiers()
+ {
+ return getPacketSequence().getPacketSequenceHeader().getIdentifiers();
+ }
+}