mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#1638 Enhances DMR decoder to include additional encryption details from two major vendor implementations. Adds Hytera short data Radio Registration Service (RRS) processing.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.util.List;
|
||||
*/
|
||||
public abstract class DataMessageWithLinkControl extends DataMessage
|
||||
{
|
||||
private LCMessage mLCMessage;
|
||||
protected LCMessage mLCMessage;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
|
||||
@@ -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]");
|
||||
|
||||
@@ -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]");
|
||||
|
||||
@@ -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]");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<LCOpcode> 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
|
||||
|
||||
@@ -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
|
||||
* <p>
|
||||
* 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<Identifier> 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)
|
||||
@@ -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};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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<Identifier> 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<Identifier> getIdentifiers()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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
|
||||
* <p>
|
||||
* 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<Identifier> 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<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkgroup());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -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<Preamble> mPreambles = new ArrayList<>();
|
||||
private List<DataBlock> mDataBlocks = new ArrayList<>();
|
||||
private final List<Preamble> mPreambles = new ArrayList<>();
|
||||
private final List<DataBlock> 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()
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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: <a href="https://patents.google.com/patent/EP2347540B1/en">...</a> - embedding encryption parameters in voice super frame
|
||||
* See patent: <a href="https://patents.google.com/patent/US8271009B2">...</a> - 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--)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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: <a href="https://patents.google.com/patent/US8271009B2">...</a> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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<Identifier> getIdentifiers()
|
||||
{
|
||||
return getPacketSequence().getPacketSequenceHeader().getIdentifiers();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user