From 5d7b73f87623e853c61149d14b7a93edddcd2053 Mon Sep 17 00:00:00 2001 From: Dennis Sheirer Date: Sat, 26 Aug 2023 07:32:46 -0400 Subject: [PATCH] #1638 Enhances DMR decoder to include additional encryption details from two major vendor implementations. Adds Hytera short data Radio Registration Service (RRS) processing. --- .../module/decode/dmr/DMRDecoderState.java | 86 +++++++++++- .../message/data/DMRDataMessageFactory.java | 14 +- .../data/DataMessageWithLinkControl.java | 2 +- .../message/data/block/DataBlock1Rate.java | 4 + .../message/data/block/DataBlock1_2Rate.java | 4 + .../message/data/block/DataBlock3_4Rate.java | 4 + .../decode/dmr/message/data/csbk/Opcode.java | 9 +- ...oprietaryDataHeader.java => PiHeader.java} | 38 ++++-- .../hytera/HyteraDataEncryptionHeader.java | 126 ++++++++++++++++++ .../MotorolaDataEncryptionHeader.java | 4 +- .../dmr/message/data/lc/LCMessageFactory.java | 50 +++---- .../decode/dmr/message/data/lc/LCOpcode.java | 11 +- ...ameters.java => EncryptionParameters.java} | 68 ++++++++-- .../message/data/lc/full/FullLCMessage.java | 2 +- .../data/lc/full/hytera/HyteraFullLC.java | 25 +--- .../hytera/HyteraGroupVoiceChannelUser.java | 44 +++--- .../data/lc/full/hytera/HyteraTerminator.java | 5 +- .../HyteraUnitToUnitVoiceChannelUser.java | 5 +- .../MotorolaArc4EncryptionParameters.java | 116 ---------------- .../message/data/packet/PacketSequence.java | 22 ++- .../packet/PacketSequenceMessageFactory.java | 38 ++++-- .../dmr/message/type/EncryptionAlgorithm.java | 82 ++++++++++++ .../voice/VoiceSuperFrameProcessor.java | 26 ++-- ...ameters.java => EncryptionParameters.java} | 37 +++-- .../voice/embedded/ShortBurstOpcode.java | 7 +- .../module/decode/event/DecodeEventType.java | 4 +- .../shortdata/HyteraShortDataPacket.java | 120 +++++++++++++++++ 27 files changed, 662 insertions(+), 291 deletions(-) rename src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/{hytera/HyteraProprietaryDataHeader.java => PiHeader.java} (65%) create mode 100644 src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java rename src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/{hytera/HyteraArc4EncryptionParameters.java => EncryptionParameters.java} (59%) delete mode 100644 src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaArc4EncryptionParameters.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/dmr/message/type/EncryptionAlgorithm.java rename src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/{Arc4EncryptionParameters.java => EncryptionParameters.java} (68%) create mode 100644 src/main/java/io/github/dsheirer/module/decode/ip/hytera/shortdata/HyteraShortDataPacket.java diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java index 6a5b4be6..0bd2c81b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java @@ -57,6 +57,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcem import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.VoteNowAdvice; import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.ChannelGrant; import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage; +import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader; import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser; @@ -74,13 +75,17 @@ import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator; import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; -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.event.DecodeEvent; import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.event.PlottableDecodeEvent; 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.mototrbo.ars.ARSPacket; +import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket; +import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket; import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest; import io.github.dsheirer.util.PacketUtil; @@ -320,6 +325,32 @@ public class DMRDecoderState extends TimeslotDecoderState .build(); broadcast(smsEvent); } + //Hytera Short Data - Radio Registration Service (RRS) + else if(packet.getPacket() instanceof HyteraShortDataPacket hsdp) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers()); + + StringBuilder sb = new StringBuilder(); + sb.append("HYTERA"); + + if(hsdp.getPacketSequence().isEncrypted()) + { + HyteraDataEncryptionHeader hdeh = (HyteraDataEncryptionHeader)hsdp.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(hsdp.getMessage().toHexString()); + + + DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp()) + .identifiers(mic) + .timeslot(getTimeslot()) + .details(sb.toString()) + .build(); + broadcast(shortDataEvent); + } //Unknown Hytera Long Data Service Token Message else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket) { @@ -328,10 +359,46 @@ public class DMRDecoderState extends TimeslotDecoderState DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp()) .identifiers(mic) .timeslot(getTimeslot()) - .details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString()) + .details("HYTERA LONG DATA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString()) .build(); broadcast(unknownTokenEvent); } + //Motorola ARS + else if(packet.getPacket() instanceof ARSPacket ars) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers()); + + DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp()) + .identifiers(mic) + .timeslot(getTimeslot()) + .details(ars.toString()) + .build(); + broadcast(shortDataEvent); + } + //Motorola LRRP + else if(packet.getPacket() instanceof LRRPPacket lrrp) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers()); + + DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.LRRP, packet.getTimestamp()) + .identifiers(mic) + .timeslot(getTimeslot()) + .details(lrrp.toString()) + .build(); + broadcast(shortDataEvent); + } + //Motorola XCMP + else if(packet.getPacket() instanceof XCMPPacket xcmp) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers()); + + DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.XCMP, packet.getTimestamp()) + .identifiers(mic) + .timeslot(getTimeslot()) + .details(xcmp.toString()) + .build(); + broadcast(shortDataEvent); + } else { DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp()) @@ -380,7 +447,7 @@ public class DMRDecoderState extends TimeslotDecoderState { EmbeddedParameters embedded = voiceEmb.getEmbeddedParameters(); - if(embedded.getShortBurst() instanceof Arc4EncryptionParameters arc4) + if(embedded.getShortBurst() instanceof EncryptionParameters arc4) { updateEncryptedCall(arc4, true, voiceEmb.getTimestamp()); } @@ -972,6 +1039,15 @@ public class DMRDecoderState extends TimeslotDecoderState { switch(message.getOpcode()) { + case FULL_ENCRYPTION_PARAMETERS: + if(message instanceof io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters ep) + { + if(mCurrentCallEvent != null) + { + mCurrentCallEvent.setDetails(ep.getDetails()); + } + } + break; case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION: if(message instanceof CapacityPlusRestChannel) { @@ -1141,7 +1217,7 @@ public class DMRDecoderState extends TimeslotDecoderState * @param encryptionParameters decoded from the Voice Frame F * @param isGroup true for group or false for individual call. */ - private void updateEncryptedCall(Arc4EncryptionParameters encryptionParameters, boolean isGroup, long timestamp) + private void updateEncryptedCall(EncryptionParameters encryptionParameters, boolean isGroup, long timestamp) { if(mCurrentCallEvent != null) { @@ -1151,7 +1227,7 @@ public class DMRDecoderState extends TimeslotDecoderState { details = encryptionParameters.toString(); } - else if(!details.contains(encryptionParameters.toString())) + else if(!details.contains(encryptionParameters.toString()) && !details.contains("ENCRYPTION")) { details += " " + encryptionParameters; } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java index 0b13a0cc..f21c6a04 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DMRDataMessageFactory.java @@ -33,8 +33,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory import io.github.dsheirer.module.decode.dmr.message.data.header.ConfirmedDataHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.DataHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.DefinedShortDataHeader; -import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage; import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader; +import io.github.dsheirer.module.decode.dmr.message.data.header.PiHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.RawShortDataHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.ResponseDataHeader; @@ -43,7 +43,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.StatusDataHeader import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.UnconfirmedDataHeader; import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader; -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.data.mbc.MBCContinuationBlock; @@ -98,7 +98,7 @@ public class DMRDataMessageFactory return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot); case CHANNEL_CONTROL_ENC_HEADER: case PI_HEADER: - return new HeaderMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot); + return new PiHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot); case VOICE_HEADER: return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot); case DATA_ENC_HEADER: @@ -138,10 +138,10 @@ public class DMRDataMessageFactory return mprdh; } case HYTERA_68: - HyteraProprietaryDataHeader hpdh = new HyteraProprietaryDataHeader(pattern, payload, - cach, slotType, timestamp, timeslot); - hpdh.setValid(valid); - return hpdh; + HyteraDataEncryptionHeader hsdh = new HyteraDataEncryptionHeader(pattern, payload, + cach, slotType, timestamp, timeslot); + hsdh.setValid(valid); + return hsdh; default: ProprietaryDataHeader pdh = new ProprietaryDataHeader(pattern, payload, cach, slotType, timestamp, timeslot); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java index c51d6f93..5a20131b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/DataMessageWithLinkControl.java @@ -34,7 +34,7 @@ import java.util.List; */ public abstract class DataMessageWithLinkControl extends DataMessage { - private LCMessage mLCMessage; + protected LCMessage mLCMessage; /** * Constructs an instance. diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java index 39a94365..e7194f38 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1Rate.java @@ -56,6 +56,10 @@ public class DataBlock1Rate extends DataBlock { StringBuilder sb = new StringBuilder(); sb.append("CC:").append(getSlotType().getColorCode()); + if(hasRAS()) + { + sb.append(" RAS:").append(getBPTCReservedBits()); + } if(!isValid()) { sb.append(" [CRC ERROR]"); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java index 6c5e8453..a575083a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock1_2Rate.java @@ -56,6 +56,10 @@ public class DataBlock1_2Rate extends DataBlock { StringBuilder sb = new StringBuilder(); sb.append("CC:").append(getSlotType().getColorCode()); + if(hasRAS()) + { + sb.append(" RAS:").append(getBPTCReservedBits()); + } if(!isValid()) { sb.append(" [CRC ERROR]"); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java index f1c93db7..145cd497 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/block/DataBlock3_4Rate.java @@ -57,6 +57,10 @@ public class DataBlock3_4Rate extends DataBlock { StringBuilder sb = new StringBuilder(); sb.append("CC:").append(getSlotType().getColorCode()); + if(hasRAS()) + { + sb.append(" RAS:").append(getBPTCReservedBits()); + } if(!isValid()) { sb.append(" [CRC ERROR]"); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java index ec8a601c..0bd0c438 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/Opcode.java @@ -27,7 +27,6 @@ import java.util.TreeMap; /** * Control Signalling Block (CSBK) Opcode enumeration - * * ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B * ETSI TS 102 361-4 DMR Trunking Protocol, Annex B */ @@ -91,6 +90,8 @@ public enum Opcode HYTERA_08_ACKNOWLEDGE(Vendor.HYTERA_8, 32, "HYTERA 08 ACKNOWLEDGE"), HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "HYTERA 08 ANNOUNCEMENT"), + //CSBKO 44 & 47 observed on Tier3 interleaved in voice group call terminator sequence. 44 was continuously transmitted + //and 47 was only transmitted 3x times in succession in the middle of the terminators and CSBKO 44 messages. HYTERA_08_CSBKO_44(Vendor.HYTERA_8, 44, "HYTERA 08 CSBKO 44"), HYTERA_08_CSBKO_47(Vendor.HYTERA_8, 47, "HYTERA 08 CSBKO 47"), @@ -104,9 +105,9 @@ public enum Opcode UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN"); - private Vendor mVendor; - private int mValue; - private String mLabel; + private final Vendor mVendor; + private final int mValue; + private final String mLabel; /** * Constructor diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraProprietaryDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java similarity index 65% rename from src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraProprietaryDataHeader.java rename to src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java index 3b768b5a..0a46442f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraProprietaryDataHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/PiHeader.java @@ -17,18 +17,19 @@ * **************************************************************************** */ -package io.github.dsheirer.module.decode.dmr.message.data.header.hytera; +package io.github.dsheirer.module.decode.dmr.message.data.header; import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; import io.github.dsheirer.module.decode.dmr.message.CACH; import io.github.dsheirer.module.decode.dmr.message.data.SlotType; -import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader; +import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage; +import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory; /** - * Hytera Proprietary Data Header + * PI/Encryption Header with Link Control */ -public class HyteraProprietaryDataHeader extends ProprietaryDataHeader +public class PiHeader extends HeaderMessage { /** * Constructs an instance. @@ -40,23 +41,42 @@ public class HyteraProprietaryDataHeader extends ProprietaryDataHeader * @param timestamp message was received * @param timeslot for the DMR burst */ - public HyteraProprietaryDataHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot) + public PiHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot) { super(syncPattern, message, cach, slotType, timestamp, timeslot); } + /** + * Access the embedded link control message + */ + public LCMessage getLCMessage() + { + if(mLCMessage == null) + { + //Overrides default to ignore opcode and construct an encryption parameters message. + mLCMessage = LCMessageFactory.createFullEncryption(getMessage(), getTimestamp(), getTimeslot()); + } + + return mLCMessage; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("CC:").append(getSlotType().getColorCode()); + if(!isValid()) { sb.append(" [CRC ERROR]"); } - sb.append(" HYTERA PROPRIETARY DATA HEADER"); - sb.append(" SAP:").append(getServiceAccessPoint()); - sb.append(" MSG:").append(getMessage().toHexString()); + + sb.append(getSlotType()); + + if(hasRAS()) + { + sb.append(" RAS:").append(getBPTCReservedBits()).append(" "); + } + sb.append(" ").append(getLCMessage()); return sb.toString(); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java new file mode 100644 index 00000000..2f579d78 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/hytera/HyteraDataEncryptionHeader.java @@ -0,0 +1,126 @@ +/* + * ***************************************************************************** + * 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.header.hytera; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.message.CACH; +import io.github.dsheirer.module.decode.dmr.message.data.SlotType; +import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader; +import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm; + +/** + * Hytera Proprietary Data Header for SAP Short Data + */ +public class HyteraDataEncryptionHeader extends ProprietaryDataHeader +{ + private static final int[] ALGORITHM = new int[]{16, 17, 18, 19, 20, 21, 22, 23}; + private static final int[] KEY = new int[]{24, 25, 26, 27, 28, 29, 30, 31}; + private static final int[] INITIALIZATION_VECTOR = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}; + private static final int[] UNKNOWN = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; + + /** + * Constructs an instance. + * + * @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA + * @param message containing extracted 196-bit payload. + * @param cach for the DMR burst + * @param slotType for this data message + * @param timestamp message was received + * @param timeslot for the DMR burst + */ + public HyteraDataEncryptionHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot) + { + super(syncPattern, message, cach, slotType, timestamp, timeslot); + } + + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("CC:").append(getSlotType().getColorCode()); + if(!isValid()) + { + sb.append(" [CRC ERROR]"); + } + sb.append(" HYTERA DATA ENCRYPTION HEADER - SAP:").append(getServiceAccessPoint()); + + switch(getAlgorithm()) + { + case NO_ENCRYPTION: + sb.append(" ALGORITHM:UNENCRYPTED"); + break; + case UNKNOWN: + sb.append(" ALGORITHM:").append(getMessage().getInt(ALGORITHM)); + break; + default: + sb.append(" ALGORITHM:").append(getAlgorithm()); + break; + } + + if(getAlgorithm() != EncryptionAlgorithm.NO_ENCRYPTION) + { + sb.append(" KEY:").append(getKeyId()); + sb.append(" IV:").append(getIV()); + sb.append(" UNK:").append(getUnknown()); + } + + sb.append(" MSG:").append(getMessage().toHexString()); + return sb.toString(); + } + + /** + * Encryption algorithm. + * @return algorithm or UNKNOWN if the value is not known. + */ + public EncryptionAlgorithm getAlgorithm() + { + return EncryptionAlgorithm.fromValue(getMessage().getInt(ALGORITHM)); + } + + /** + * Encryption key ID (1-255). + * @return key ID + */ + public int getKeyId() + { + return getMessage().getInt(KEY); + } + + /** + * Hex string representation of the initialization vector + * @return IV in hex string. + */ + public String getIV() + { + return getMessage().getHex(INITIALIZATION_VECTOR, 8); + } + + /** + * Unknown field. + * @return hex string from the field contents. + */ + public String getUnknown() + { + return getMessage().getHex(UNKNOWN, 4); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java index 0bad828a..c81de344 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java @@ -64,8 +64,8 @@ public class MotorolaDataEncryptionHeader extends ProprietaryDataHeader sb.append(" MOTOROLA DATA ENCRYPTION HEADER"); sb.append(" SAP:").append(getServiceAccessPoint()); sb.append(" ALGORITHM?:").append(getAlgorithm()); - sb.append(" KEY?:").append(getKeyId()); - sb.append(" IV?:").append(getInitializationVector()); + sb.append(" KEY:").append(getKeyId()); + sb.append(" IV:").append(getInitializationVector()); sb.append(" UNK:").append(getUnknown()); sb.append(" MSG:").append(getMessage().toHexString()); return sb.toString(); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java index 92669470..b5eacb9f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java @@ -23,6 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.edac.CRCDMR; import io.github.dsheirer.edac.Checksum_5_DMR; import io.github.dsheirer.edac.ReedSolomon_12_9_4_DMR; +import io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser; @@ -33,13 +34,11 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasHead import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TerminatorData; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMessage; -import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraArc4EncryptionParameters; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser; -import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaArc4EncryptionParameters; import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser; import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ActivityUpdateMessage; import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel; @@ -61,8 +60,20 @@ public class LCMessageFactory { private final static Logger mLog = LoggerFactory.getLogger(LCMessageFactory.class); private static final ReedSolomon_12_9_4_DMR REED_SOLOMON_12_9_4_DMR = new ReedSolomon_12_9_4_DMR(); - private static int TERMINATOR_LINK_CONTROL_CRC_MASK = 0x99; - private static int VOICE_LINK_CONTROL_CRC_MASK = 0x96; + private static final int TERMINATOR_LINK_CONTROL_CRC_MASK = 0x99; + private static final int VOICE_LINK_CONTROL_CRC_MASK = 0x96; + + /** + * Creates a full link control message specifically for the PI_HEADER message type. + * @param message bits + * @param timestamp of the original message + * @param timeslot of the original message + * @return encryption parameters link control. + */ + public static FullLCMessage createFullEncryption(CorrectedBinaryMessage message, long timestamp, int timeslot) + { + return new EncryptionParameters(message, timestamp, timeslot); + } /** * Creates a full link control message @@ -123,34 +134,9 @@ public class LCMessageFactory case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER: flc = new CapacityPlusWideAreaVoiceChannelUser(message, timestamp, timeslot); break; - case FULL_ARC4_ENCRYPTION_PARAMETERS: - boolean isHytera = false; - - //This is apparently now a DMR standard FLC opcode, even though it's using Motorola's vendor ID (0x10). - //As observed on a known Hytera system, it's not using the standard FLC RS-12/9/4 check, which fails. - //It's apparently using the CRC-CCITT with 0x9696 initial fill. Not sure if both Hytera and Motorola - // are using this FEC. But, by using only 16 bits for checksum versus the standad 24, they're able to - // include a full 24-bit group/radio ID in the message. For now we'll identify as either Motorola - //(using reed solomon) or Hytera (using crc-ccitt). - if(!valid) - { - int bitErrors = CRCDMR.correctCCITT80(message, 0, 80, 0x9696); - - if(bitErrors < 2) - { - valid = true; - isHytera = true; - } - } - - if(isHytera) - { - flc = new HyteraArc4EncryptionParameters(message, timestamp, timeslot); - } - else - { - flc = new MotorolaArc4EncryptionParameters(message, timestamp, timeslot); - } + case FULL_ENCRYPTION_PARAMETERS: + flc = new EncryptionParameters(message, timestamp, timeslot); + valid = flc.isValid(); break; case FULL_HYTERA_GROUP_VOICE_CHANNEL_USER: flc = new HyteraGroupVoiceChannelUser(message, timestamp, timeslot); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java index e5377e4b..457d6183 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java @@ -27,7 +27,6 @@ import java.util.TreeMap; /** * Full and Short Link Control Opcode enumeration - * * ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B.1 & B.2 * ETSI TS 102 361-4 DMR Trunking Protocol, Annex B.1 & B.2 */ @@ -49,7 +48,7 @@ public enum LCOpcode //Observed on Cap+ Multi-Site System during an encrypted voice call FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"), //Observed on Cap+ Multi-Site System during an encrypted voice call - FULL_ARC4_ENCRYPTION_PARAMETERS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 33, "ARC4/EP ENCRYPTION PARAMETERS"), + FULL_ENCRYPTION_PARAMETERS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 33, "ENCRYPTION PARAMETERS"), //Cap+ opcodes from https://forums.radioreference.com/threads/understanding-capacity-plus-trunking-some-more.452566/ //FLCO 0: Group Call Maintenance //FLCO 3: Private Call Maintenance (TermLC) @@ -81,10 +80,10 @@ public enum LCOpcode private static final EnumSet 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(); + } +}