+
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/edac/BPTC_16_2.java b/src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
new file mode 100644
index 000000000..9840e1c63
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
@@ -0,0 +1,125 @@
+/*
+ * *****************************************************************************
+ * 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.edac;
+
+import io.github.dsheirer.bits.BinaryMessage;
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+
+/**
+ * Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field.
+ *
+ * See ETSI TS 102 361-1 B.2.2.1 and B.2.2.2
+ */
+public class BPTC_16_2
+{
+ private static final int[] DEINTERLEAVE = new int[]{0, 24, 1, 25, 2, 26, 3, 27, 4, 28, 5, 29, 6, 30, 7, 31, 8, 16,
+ 9, 17, 10, 18, 11, 19, 12, 20, 13, 21, 14, 22, 15, 23};
+
+ /**
+ * Unscramble and perform FEC checks per paragraph B.2.2.1 for Non-Reverse Channel Single Burst
+ *
+ * @param message with 32 interleaved bits.
+ * @return descrambled and error checked message or null if the process fails or there are too many errors.
+ */
+ public static CorrectedBinaryMessage decodeShortBurst(CorrectedBinaryMessage message)
+ {
+ CorrectedBinaryMessage deinterleaved = deinterleave(message);
+ int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
+
+ if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
+ {
+ return null;
+ }
+
+ //Check for even parity. Bits 0-15 should be the same as bits 16-31.
+ for(int x = 0; x < 16; x++)
+ {
+ if(deinterleaved.get(x) ^ deinterleaved.get(x + 16))
+ {
+ return null;
+ }
+ }
+
+ return deinterleaved;
+ }
+
+ /**
+ * Unscramble and perform FEC checks per paragraph B.2.2.2 for Reverse Channel Single Burst
+ *
+ * @param binaryMessage with 32 interleaved bits.
+ * @return descrambled and error checked message or null if the process fails or there are too many errors.
+ */
+ public static CorrectedBinaryMessage decodeReverseChannel(CorrectedBinaryMessage message)
+ {
+ CorrectedBinaryMessage deinterleaved = deinterleave(message);
+ System.out.println(" DEINTER: " + deinterleaved.toHexString());
+ int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
+ System.out.println(" DECODED: " + deinterleaved.toHexString());
+ System.out.println("FEC:" + fec);
+ if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
+ {
+ return null;
+ }
+
+ //Check for odd parity. Bits 0-15 should be opposite of bits 16-31.
+ for(int x = 0; x < 16; x++)
+ {
+ if(deinterleaved.get(x) == deinterleaved.get(x + 16))
+ {
+ return null;
+ }
+ }
+
+ return deinterleaved;
+ }
+
+ /**
+ * Performs deinterleave of the interleaved message.
+ *
+ * @param original to deinterleave
+ * @return deinterleaved message
+ */
+ public static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage original)
+ {
+ CorrectedBinaryMessage delinterleaved = new CorrectedBinaryMessage(32);
+ for(int x = 0; x < 32; x++)
+ {
+ if(original.get(x))
+ {
+ delinterleaved.set(DEINTERLEAVE[x]);
+ }
+ }
+
+ return delinterleaved;
+ }
+
+ public static void main(String[] args)
+ {
+ String[] msgs = new String[]{"05030A03", "35003A00", "1C6D2C9E"};
+
+ for(String msg : msgs)
+ {
+ CorrectedBinaryMessage original = new CorrectedBinaryMessage(BinaryMessage.loadHex(msg));
+ System.out.println("ORIGINAL: " + original.toHexString());
+ CorrectedBinaryMessage decoded = decodeReverseChannel(original);
+ System.out.println("-------------------------");
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/edac/CRCUtil.java b/src/main/java/io/github/dsheirer/edac/CRCUtil.java
index 4b0b08a2a..be7382df4 100644
--- a/src/main/java/io/github/dsheirer/edac/CRCUtil.java
+++ b/src/main/java/io/github/dsheirer/edac/CRCUtil.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -226,17 +226,19 @@ public static void main(String[] args)
{
mLog.debug("Starting");
- //DMR message
-// String raw = "100110010001000000001001000000001111011011010000011001010000000000000000000000001100001000001011";
- String raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101001001111010001110";
- mLog.debug(raw);
- raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101000000000000000000";
- BinaryMessage message = BinaryMessage.load(raw);
- mLog.debug(message.toString());
-
- long polynomial = 0x11021l;
- decode(message, 0, 80, polynomial, 16);
- mLog.debug(message.toString());
- mLog.debug("Finished");
+ long poly = 0x13l;
+ long[] checksums = generate(32, 4, poly, 0, true);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("private static int[] CHECKSUMS = new int[]{");
+ for(long checksum: checksums)
+ {
+ sb.append("0x").append(Long.toHexString(checksum).toUpperCase());
+ sb.append(",");
+ }
+
+ sb.append("};");
+
+ System.out.println("Checksums:\n" + sb);
}
}
diff --git a/src/main/java/io/github/dsheirer/edac/Golay24.java b/src/main/java/io/github/dsheirer/edac/Golay24.java
index 44ad8a504..b6735d961 100644
--- a/src/main/java/io/github/dsheirer/edac/Golay24.java
+++ b/src/main/java/io/github/dsheirer/edac/Golay24.java
@@ -1,3 +1,22 @@
+/*
+ * *****************************************************************************
+ * 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.edac;
import io.github.dsheirer.bits.BinaryMessage;
@@ -5,27 +24,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-/*******************************************************************************
- * SDR Trunk
- * Copyright (C) 2014 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
- * -----------------------------------------------------------------------
- * Galois24 decoder based on Hank Wallace's tutorial/algorithm located at:
- * http://www.aqdi.com/golay.htm
- ******************************************************************************/
-
/**
* Galois 24/12/7 decoder
*/
@@ -160,4 +158,17 @@ private static int getSyndrome(BinaryMessage message, int startIndex)
return (checksum ^ calculated);
}
+
+ public static void main(String[] args)
+ {
+// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20"));
+// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0"));
+ CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00"));
+
+ System.out.println("M:" + bm.toHexString());
+ int a = Golay24.checkAndCorrect(bm, 0);
+ System.out.println("M:" + bm.toHexString());
+
+ System.out.println("A:" + a);
+ }
}
diff --git a/src/main/java/io/github/dsheirer/gui/dmr/DMRRecordingViewer.java b/src/main/java/io/github/dsheirer/gui/dmr/DMRRecordingViewer.java
index f177750fe..0fd1adcbd 100644
--- a/src/main/java/io/github/dsheirer/gui/dmr/DMRRecordingViewer.java
+++ b/src/main/java/io/github/dsheirer/gui/dmr/DMRRecordingViewer.java
@@ -459,6 +459,12 @@ private TextField getFindText()
if(mFindText == null)
{
mFindText = new TextField();
+ mFindText.setOnKeyPressed(event -> {
+ if(event.getCode().equals(KeyCode.ENTER))
+ {
+ getFindButton().fire();
+ }
+ });
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
}
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 4bfd1b65a..df8c035e8 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
@@ -65,8 +65,8 @@
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.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.CapacityPlusGroupVoiceChannelUser;
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.MotorolaGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService;
@@ -646,12 +646,12 @@ private void processCSBK(CSBKMessage csbk)
}
else
{
- DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
+ DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
- mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
+ mDetectedCallEventsMap.put(channel.getValue(), event);
}
else
{
@@ -679,12 +679,12 @@ private void processCSBK(CSBKMessage csbk)
}
else
{
- DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
+ DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
- mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
+ mDetectedCallEventsMap.put(channel.getValue(), event);
}
else
{
@@ -711,12 +711,12 @@ private void processCSBK(CSBKMessage csbk)
}
else
{
- DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
+ DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel, mergedIdentifiers);
- mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
+ mDetectedCallEventsMap.put(channel.getValue(), event);
}
else
{
@@ -756,12 +756,12 @@ private void processCSBK(CSBKMessage csbk)
}
else
{
- DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
+ DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
- mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
+ mDetectedCallEventsMap.put(channel.getValue(), event);
}
else
{
@@ -807,12 +807,12 @@ private void processCSBK(CSBKMessage csbk)
}
else
{
- DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
+ DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getValue());
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
{
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
- mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent);
+ mDetectedCallEventsMap.put(channel.getValue(), detectedEvent);
}
else
{
@@ -979,10 +979,8 @@ private void processLinkControl(LCMessage message, boolean isTerminator)
}
break;
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
- if(message instanceof CapacityPlusGroupVoiceChannelUser cpgvcu)
+ if(message instanceof MotorolaGroupVoiceChannelUser cpgvcu)
{
- updateRestChannel(cpgvcu.getRestChannel());
-
if(isTerminator)
{
getIdentifierCollection().remove(Role.FROM);
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java
index 0ac7d2271..97a9f3672 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java
@@ -44,6 +44,8 @@
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
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.VoiceSuperFrameProcessor;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +61,8 @@ public class DMRMessageProcessor implements Listener
{
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
private DecodeConfigDMR mConfigDMR;
+ private VoiceSuperFrameProcessor mSuperFrameProcessor1 = new VoiceSuperFrameProcessor();
+ private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
private MBCAssembler mMBCAssembler = new MBCAssembler();
@@ -89,11 +93,45 @@ public DMRMessageProcessor(DecodeConfigDMR config)
@Override
public void receive(IMessage message)
{
+ if(message instanceof FullLCMessage flc)
+ {
+ if(flc.getTimeslot() == 1)
+ {
+ mSuperFrameProcessor1.process(flc);
+ }
+ else
+ {
+ mSuperFrameProcessor2.process(flc);
+ }
+ }
+ else if(message instanceof VoiceMessage voiceMessage)
+ {
+ if(voiceMessage.getTimeslot() == 1)
+ {
+ mSuperFrameProcessor1.process(voiceMessage);
+ }
+ else
+ {
+ mSuperFrameProcessor2.process(voiceMessage);
+ }
+ }
+ else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid())
+ {
+ if(dmrBurst.getTimeslot() == 1)
+ {
+ mSuperFrameProcessor1.reset();
+ }
+ else
+ {
+ mSuperFrameProcessor2.reset();
+ }
+ }
+
//Enrich messages that carry DMR Logical Slot Number channels with LCN to frequency mappings
if(message instanceof ITimeslotFrequencyReceiver)
{
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
- int[] lsns = receiver.getLogicalTimeslotNumbers();
+ int[] lsns = receiver.getLogicalSlotNumbers();
List timeslotFrequencies = new ArrayList<>();
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRNetworkConfigurationMonitor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRNetworkConfigurationMonitor.java
index a278f6ecb..857d46f7f 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRNetworkConfigurationMonitor.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRNetworkConfigurationMonitor.java
@@ -279,7 +279,7 @@ public void process(CSBKMessage csbk)
*/
private void addDmrChannel(DMRChannel dmrChannel)
{
- mObservedChannelMap.put(dmrChannel.getLogicalSlotNumber(), dmrChannel);
+ mObservedChannelMap.put(dmrChannel.getValue(), dmrChannel);
}
public void reset()
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java
index eea406ff8..3b64a8e8a 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java
@@ -322,7 +322,7 @@ public void processEndChannelGrant()
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
Opcode opcode, long timestamp, boolean encrypted)
{
- int lsn = channel.getLogicalSlotNumber();
+ int lsn = channel.getValue();
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java
index 676001d29..fa38204f6 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/audio/DMRCallSequenceRecorder.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2021 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
@@ -32,17 +32,16 @@
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
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.HyteraUnitToUnitVoiceChannelUser;
-import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
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.MotorolaGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
+import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.List;
-
/**
* DMR AMBE Frame recorder generates call sequence recordings containing JSON representations of audio
* frames, optional encryption and call identifiers.
@@ -207,7 +206,7 @@ private void process(FullLCMessage message)
switch(message.getOpcode())
{
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
- if(message instanceof CapacityPlusGroupVoiceChannelUser cpvcu)
+ if(message instanceof MotorolaGroupVoiceChannelUser cpvcu)
{
if(mCallSequence == null)
{
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRAbsoluteChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRAbsoluteChannel.java
index 93b48d7da..bf773bf1b 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRAbsoluteChannel.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRAbsoluteChannel.java
@@ -1,23 +1,20 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2023 Dennis Sheirer
*
- * * ******************************************************************************
- * * Copyright (C) 2014-2019 Dennis Sheirer
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU General Public License as published by
- * * the Free Software Foundation, either version 3 of the License, or
- * * (at your option) any later version.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU General Public License for more details.
- * *
- * * You should have received a copy of the GNU General Public License
- * * along with this program. If not, see
- * * *****************************************************************************
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.channel;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRChannel.java
index bdfc361d8..e4653a2ca 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRChannel.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRChannel.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
@@ -56,9 +56,9 @@ public Protocol getProtocol()
}
/**
- * Repeater number for the channel.
+ * Repeater number or channel number for the channel.
*/
- public int getRepeater()
+ public int getChannel()
{
return getValue();
}
@@ -72,36 +72,6 @@ public int getTimeslot()
return mTimeslot;
}
- /**
- * Logical slot number for this channel. LSN is a 1-based index value where repeater one, timeslot 1 is
- * LSN 1, timeslot 2 is LSN 2, etc.
- *
- * Formula: LSN = ((channel - 1) * 2) + timeslot
- *
- * @return logical slot number, a 1-based index value
- */
- public int getLogicalSlotNumber()
- {
- int repeater = getRepeater();
-
- if(repeater > 0)
- {
- return ((repeater - 1) * 2) + getTimeslot();
- }
-
- return 0;
- }
-
- /**
- * Returns an array of length 1 containing this channel's logical slot number
- */
- public int[] getLSNArray()
- {
- int[] logicalSlotNumbers = new int[1];
- logicalSlotNumbers[0] = getLogicalSlotNumber();
- return logicalSlotNumbers;
- }
-
/**
* Number of timeslots for the DMR channel.
* @return 2 always.
@@ -121,6 +91,13 @@ public boolean isTDMAChannel()
return true;
}
+
+ @Override
+ public String toString()
+ {
+ return "CHAN:" + getChannel() + ":" + getTimeslot();
+ }
+
/**
* Not implemented
*/
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLogicalChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLsn.java
similarity index 51%
rename from src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLogicalChannel.java
rename to src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLsn.java
index 07088f10f..1cb9c5a83 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLogicalChannel.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRLsn.java
@@ -19,29 +19,60 @@
package io.github.dsheirer.module.decode.dmr.channel;
+import java.util.List;
+
/**
- * DMR logical channel. This channel uses a logical channel number and a timeslot.
+ * DMR Logical Slot Number (LSN) channel.
+ *
+ * Note: LSN 1-16 are represented in memory using the following channel and timeslot values:
+ *
+ * LSN: CHANNEL/TIMESLOT
+ * 1: 1/1
+ * 2: 1/2
+ * 3: 2/1
+ * 4: 2/2
+ * 5: 3/1
+ * 6: 3/2
+ * 7: 4/1
+ * 8: 4/2
+ * 9: 5/1
+ * 10: 5/2
+ * 11: 6/1
+ * 12: 6/2
+ * 13: 7/1
+ * 14: 7/2
+ * 15: 8/1
+ * 16: 8/2
*/
-public class DMRLogicalChannel extends DMRChannel
+public class DMRLsn extends DMRChannel implements ITimeslotFrequencyReceiver
{
private TimeslotFrequency mTimeslotFrequency;
/**
- * Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
- * calculated logical slot value for visual compatibility for users.
+ * Constructs an instance
*
- * @param channel number or repeater number
- * @param logicalSlotNumber - zero based index.
+ * @param lsn in range 1 - 16
*/
- public DMRLogicalChannel(int channel, int timeslot)
+ public DMRLsn(int lsn)
+ {
+ super(((lsn - 1) / 2) + 1, ((lsn - 1) % 2) + 1);
+ }
+
+ @Override
+ public String toString()
{
- super(channel, timeslot);
+ return "LSN:" + getLsn();
}
/**
- * Downlink frequency
- * @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
+ * Logical Slot Number
+ * @return lsn
*/
+ public int getLsn()
+ {
+ return ((getChannel() - 1) * 2) + getTimeslot();
+ }
+
@Override
public long getDownlinkFrequency()
{
@@ -53,10 +84,6 @@ public long getDownlinkFrequency()
return 0;
}
- /**
- * Uplink frequency
- * @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
- */
@Override
public long getUplinkFrequency()
{
@@ -68,23 +95,40 @@ public long getUplinkFrequency()
return 0;
}
+ @Override
+ public int[] getLogicalSlotNumbers()
+ {
+ return new int[]{getLsn()};
+ }
+
/**
- * Sets the timeslot frequency mapping
- * @param timeslotFrequency
+ * Sets the lsn to frequency mapper value.
+ * @param timeslotFrequency to set
*/
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
{
mTimeslotFrequency = timeslotFrequency;
}
- /**
- * Formatted channel number
- */
- public String toString()
+ @Override
+ public void apply(List timeslotFrequencies)
{
- StringBuilder sb = new StringBuilder();
- sb.append("LSN:").append(getLogicalSlotNumber());
-// sb.append(" LCN:").append(getValue());
- return sb.toString();
+ for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
+ {
+ if(timeslotFrequency.getNumber() == getLsn())
+ {
+ setTimeslotFrequency(timeslotFrequency);
+ return;
+ }
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ for(int x = 1; x <= 16; x++)
+ {
+ DMRLsn lsn = new DmrRestLsn(x);
+ System.out.println(lsn);
+ }
}
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRTier3Channel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRTier3Channel.java
index 824b95dcb..3955bc46b 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRTier3Channel.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DMRTier3Channel.java
@@ -1,16 +1,37 @@
+/*
+ * *****************************************************************************
+ * 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.channel;
/**
- * DMR Tier III Trunking Channel
+ * DMR Tier III Trunking channel. This channel uses a logical channel number and a timeslot.
*/
-public class DMRTier3Channel extends DMRLogicalChannel
+public class DMRTier3Channel extends DMRChannel
{
+ private TimeslotFrequency mTimeslotFrequency;
+
/**
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
* calculated logical slot value for visual compatibility for users.
*
* @param channel number or repeater number
- * @param timeslot
+ * @param logicalSlotNumber - zero based index.
*/
public DMRTier3Channel(int channel, int timeslot)
{
@@ -18,23 +39,51 @@ public DMRTier3Channel(int channel, int timeslot)
}
/**
- * Logical slot number for this channel.
- *
- * Formula: LSN = (channel * 2) + timeslot
- *
- * @return logical slot number, a 1-based index value
+ * Downlink frequency
+ * @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
*/
@Override
- public int getLogicalSlotNumber()
+ public long getDownlinkFrequency()
{
- int repeater = getRepeater();
+ if(mTimeslotFrequency != null)
+ {
+ return mTimeslotFrequency.getDownlinkFrequency();
+ }
+
+ return 0;
+ }
- if(repeater > 0)
+ /**
+ * Uplink frequency
+ * @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
+ */
+ @Override
+ public long getUplinkFrequency()
+ {
+ if(mTimeslotFrequency != null)
{
- return ((repeater) * 2) + getTimeslot();
+ return mTimeslotFrequency.getUplinkFrequency();
}
return 0;
}
+ /**
+ * Sets the timeslot frequency mapping
+ * @param timeslotFrequency
+ */
+ public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
+ {
+ mTimeslotFrequency = timeslotFrequency;
+ }
+
+ /**
+ * Formatted channel number
+ */
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" LCN:").append(getValue());
+ return sb.toString();
+ }
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DmrRestLsn.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DmrRestLsn.java
new file mode 100644
index 000000000..c29135a20
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/DmrRestLsn.java
@@ -0,0 +1,42 @@
+/*
+ * *****************************************************************************
+ * 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.channel;
+
+/**
+ * DMR Rest Channel logical slot number (LSN).
+ */
+public class DmrRestLsn extends DMRLsn
+{
+ /**
+ * Constructs an instance
+ *
+ * @param lsn in range 1 - 16
+ */
+ public DmrRestLsn(int lsn)
+ {
+ super(lsn);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "REST:" + getLsn();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/ITimeslotFrequencyReceiver.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/ITimeslotFrequencyReceiver.java
index a1f5b245d..fffd15418 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/ITimeslotFrequencyReceiver.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/ITimeslotFrequencyReceiver.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
@@ -26,7 +26,7 @@ public interface ITimeslotFrequencyReceiver
/**
* Provides the logical slot number(s) that require a matching timeslot frequency mapping
*/
- public int[] getLogicalTimeslotNumbers();
+ public int[] getLogicalSlotNumbers();
/**
* Applies the list of timeslot frequency mappings to the implementer
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/TimeslotFrequency.java b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/TimeslotFrequency.java
index 08adf174d..1a5a4e810 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/channel/TimeslotFrequency.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/channel/TimeslotFrequency.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
@@ -31,7 +31,7 @@
import javafx.util.Callback;
/**
- * Maps a timeslot number to a pair of channel frequency values
+ * Maps a logical slot number (LSN) to a pair of channel frequency values
*/
public class TimeslotFrequency
{
@@ -73,7 +73,7 @@ public IntegerProperty getNumberProperty()
* Downlink Frequency property
*/
@JsonIgnore
- public LongProperty downlinkFrequencyPropertyProperty()
+ public LongProperty downlinkFrequencyProperty()
{
return mDownlinkFrequencyProperty;
}
@@ -168,7 +168,7 @@ public String toString()
*/
public static Callback extractor()
{
- return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyPropertyProperty(),
+ return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyProperty(),
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
}
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java
index 5a8c8f907..06db716dd 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/DMRBurst.java
@@ -1,5 +1,25 @@
+/*
+ * *****************************************************************************
+ * 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;
+import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
/**
@@ -53,4 +73,13 @@ public DMRSyncPattern getSyncPattern()
{
return mSyncPattern;
}
+
+ /**
+ * Extracts the sync payload from between the two payload fragments.
+ * @return binary message containing just the sync payload bits (64).
+ */
+ public BinaryMessage getSyncPayload()
+ {
+ return getMessage().get(SYNC_START, PAYLOAD_2_START);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/IServiceOptionsProvider.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/IServiceOptionsProvider.java
new file mode 100644
index 000000000..ba2ff3d4a
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/IServiceOptionsProvider.java
@@ -0,0 +1,34 @@
+/*
+ * *****************************************************************************
+ * 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;
+
+import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
+
+/**
+ * DMR message that exposes a service options configuration
+ */
+public interface IServiceOptionsProvider
+{
+ /**
+ * Service Options
+ * @return service options
+ */
+ ServiceOptions getServiceOptions();
+}
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 3fd873855..0b13a0cca 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
@@ -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
@@ -45,7 +45,7 @@
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.motorola.MNISProprietaryDataHeader;
-import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaProprietaryDataHeader;
+import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
@@ -132,7 +132,7 @@ public static DataMessage create(DMRSyncPattern pattern, CorrectedBinaryMessage
}
else
{
- MotorolaProprietaryDataHeader mprdh = new MotorolaProprietaryDataHeader(pattern,
+ MotorolaDataEncryptionHeader mprdh = new MotorolaDataEncryptionHeader(pattern,
payload, cach, slotType, timestamp, timeslot);
mprdh.setValid(valid);
return mprdh;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java
index 9f4dc5b38..409d10bad 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusNeighbors.java
@@ -22,7 +22,7 @@
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
@@ -39,7 +39,6 @@
public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] LC_START_STOP = new int[]{16, 17};
- private static final int TIMESLOT = 18;
private static final int[] REST_LSN = new int[]{19, 20, 21, 22, 23};
private static final int ASYNC = 24;
private static final int[] SITE = new int[]{25, 26, 27, 28};
@@ -58,7 +57,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
private static final int[] NEIGHBOR_6_REST = new int[]{76, 77, 78, 79};
- private DMRLogicalChannel mRestChannel;
+ private DmrRestLsn mRestChannel;
private DMRSite mSite;
private List mIdentifiers;
@@ -286,37 +285,23 @@ public int getRestLSN()
return getMessage().getInt(REST_LSN);
}
- /**
- * Rest repeater
- */
- public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
-
- /**
- * Rest timeslot
- * @return timeslot 1 or 2
- */
- public int getRestTimeslot()
- {
- return (getRestLSN() % 2 == 0) ? 2 : 1;
- }
-
/**
* DMR Channel
*/
- public DMRLogicalChannel getRestChannel()
+ public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
- mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
+ mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
}
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- return getRestChannel().getLSNArray();
+ return getRestChannel().getLogicalSlotNumbers();
}
/**
@@ -329,7 +314,7 @@ public void apply(List timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
- if(timeslotFrequency.getNumber() == getRestChannel().getLogicalSlotNumber())
+ if(timeslotFrequency.getNumber() == getRestChannel().getValue())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java
index 03f968e23..d42a49543 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java
@@ -22,7 +22,7 @@
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -39,13 +39,11 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
private static final int[] BYTE = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
private static final int[] TWO_BYTES = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
private static final int[] SEGMENT_INDICATOR = new int[]{16, 17};
- private static final int TIMESLOT = 18;
- private static final int RESERVED = 19;
- private static final int[] REST_LSN = new int[]{20, 21, 22, 23};
+ private static final int[] REST_LSN = new int[]{19, 20, 21, 22, 23};
private static final int[] LSN_VOICE_BITMAP = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int LSN_1_8_BITMAP_START = 24;
- private DMRLogicalChannel mRestChannel;
+ private DmrRestLsn mRestChannel;
private List mIdentifiers;
/**
@@ -80,7 +78,7 @@ public String toString()
sb.append(" RAS:").append(getBPTCReservedBits());
}
- sb.append(" CSBK CAP+ SITE STATUS REST ").append(getRestChannel());
+ sb.append(" CSBK CAP+ SITE STATUS ").append(getRestChannel());
sb.append(" ").append(getSegmentIndicator());
sb.append(" ");
@@ -277,11 +275,11 @@ public SegmentIndicator getSegmentIndicator()
/**
* Current rest channel for this site.
*/
- public DMRLogicalChannel getRestChannel()
+ public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
- mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
+ mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
@@ -297,31 +295,13 @@ public int getRestLSN()
return getMessage().getInt(REST_LSN);
}
- /**
- * Rest Channel Repeater
- */
- public int getRestRepeater()
- {
- return (int) Math.ceil(getRestLSN() / 2.0);
- }
-
- /**
- * Rest Channel Timeslot
- *
- * @return 1 or 2
- */
- public int getRestTimeslot()
- {
- return (getRestLSN() % 2 == 0) ? 2 : 1;
- }
-
/**
* Logical slot numbers that require slot to frequency mappings.
*/
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- return getRestChannel().getLSNArray();
+ return getRestChannel().getLogicalSlotNumbers();
}
/**
@@ -334,7 +314,7 @@ public void apply(List timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
- if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
+ if(getRestChannel().getValue() == timeslotFrequency.getNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java
index 019840393..b2e56521d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusDataChannelGrant.java
@@ -23,7 +23,7 @@
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
@@ -40,14 +40,13 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
{
private static final int[] TARGET_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39};
- private static final int[] REPEATER = new int[]{40, 41, 42, 43};
- private static final int[] CHANNEL_GRANT_TIMESLOT = new int[]{44};
+ private static final int[] REPEATER = new int[]{40, 41, 42, 43, 44};
//Analysis: this field correlates to UNKNOWN_FIELD_1(bits: 40-48) in ConnectPlusTerminateChannelGrant.
private static final int[] UNKNOWN_FIELD = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private RadioIdentifier mTargetRadio;
- private DMRLogicalChannel mDMRLogicalChannel;
+ private DMRLsn mDmrLsn;
private List mIdentifiers;
/**
@@ -117,32 +116,23 @@ public int getRepeater()
return getMessage().getInt(REPEATER);
}
- /**
- * Channel grant timeslot
- * @return 1 or 2
- */
- public int getChannelGrantTimeslot()
- {
- return getMessage().getInt(CHANNEL_GRANT_TIMESLOT) + 1;
- }
-
/**
* DMR Channel
*/
- public DMRLogicalChannel getChannel()
+ public DMRLsn getChannel()
{
- if(mDMRLogicalChannel == null)
+ if(mDmrLsn == null)
{
- mDMRLogicalChannel = new DMRLogicalChannel(getRepeater(), getChannelGrantTimeslot());
+ mDmrLsn = new DMRLsn(getRepeater());
}
- return mDMRLogicalChannel;
+ return mDmrLsn;
}
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- return getChannel().getLSNArray();
+ return getChannel().getLogicalSlotNumbers();
}
/**
@@ -154,7 +144,7 @@ public void apply(List timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
- if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
+ if(timeslotFrequency.getNumber() == getChannel().getValue())
{
getChannel().setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java
index 32a9136af..a886be8e0 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusOTAAnnouncement.java
@@ -22,7 +22,7 @@
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
@@ -44,11 +44,10 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
private static final int[] MESSAGE_TYPE = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] VERSION = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] UNKNOWN = new int[]{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[] DATA_REPEATER = new int[]{64, 65, 66, 67};
- private static final int[] DATA_TIMESLOT = new int[]{68};
+ 57, 58, 59, 60, 61, 62};
+ private static final int[] DATA_REPEATER = new int[]{63, 64, 65, 66, 67};
- private DMRLogicalChannel mDataChannel;
+ private DMRLsn mDataChannel;
private List mIdentifiers;
/**
@@ -107,32 +106,23 @@ public int getDataRepeater()
return getMessage().getInt(DATA_REPEATER);
}
- /**
- * Data Timeslot
- * @return 1 or 2
- */
- public int getDataTimeslot()
- {
- return getMessage().getInt(DATA_TIMESLOT) + 1;
- }
-
/**
* DMR Channel where the data is available
*/
- public DMRLogicalChannel getDataChannel()
+ public DMRLsn getDataChannel()
{
if(mDataChannel == null)
{
- mDataChannel = new DMRLogicalChannel(getDataRepeater(), getDataTimeslot());
+ mDataChannel = new DMRLsn(getDataRepeater());
}
return mDataChannel;
}
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- return getDataChannel().getLSNArray();
+ return getDataChannel().getLogicalSlotNumbers();
}
/**
@@ -145,7 +135,7 @@ public void apply(List timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
- if(timeslotFrequency.getNumber() == getDataChannel().getLogicalSlotNumber())
+ if(timeslotFrequency.getNumber() == getDataChannel().getValue())
{
getDataChannel().setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java
index e16986c38..ea6741639 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/ConnectPlusVoiceChannelUser.java
@@ -24,7 +24,7 @@
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
@@ -44,13 +44,12 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] GROUP_ADDRESS = new int[]{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[] TRAFFIC_CHANNEL_REPEATER = new int[]{64, 65, 66, 67};
- private static final int[] TRAFFIC_CHANNEL_TIMESLOT = new int[]{68};
+ private static final int[] TRAFFIC_CHANNEL_REPEATER = new int[]{64, 65, 66, 67, 68};
private static final int[] UNKNOWN_FIELD = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
- private DMRLogicalChannel mDMRLogicalChannel;
+ private DMRLsn mDmrLsn;
private List mIdentifiers;
/**
@@ -132,32 +131,23 @@ public int getTrafficChannelRepeater()
return getMessage().getInt(TRAFFIC_CHANNEL_REPEATER);
}
- /**
- * Traffic channel timeslot
- * @return 1 or 2
- */
- public int getTrafficChannelTimeslot()
- {
- return getMessage().getInt(TRAFFIC_CHANNEL_TIMESLOT) + 1;
- }
-
/**
* DMR Channel
*/
- public DMRLogicalChannel getChannel()
+ public DMRLsn getChannel()
{
- if(mDMRLogicalChannel == null)
+ if(mDmrLsn == null)
{
- mDMRLogicalChannel = new DMRLogicalChannel(getTrafficChannelRepeater(), getTrafficChannelTimeslot());
+ mDmrLsn = new DMRLsn(getTrafficChannelRepeater());
}
- return mDMRLogicalChannel;
+ return mDmrLsn;
}
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- return getChannel().getLSNArray();
+ return getChannel().getLogicalSlotNumbers();
}
/**
@@ -169,7 +159,7 @@ public void apply(List timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
- if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
+ if(timeslotFrequency.getNumber() == getChannel().getValue())
{
getChannel().setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java
index da7e2466e..d99632c14 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/Clear.java
@@ -25,7 +25,7 @@
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -224,11 +224,11 @@ public DMRChannel getMoveToChannel()
* Logical Slot Number(s) for channels contained in this message
*/
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- if(getMoveToChannel() instanceof DMRLogicalChannel)
+ if(getMoveToChannel() instanceof DMRLsn dmrLsn)
{
- return ((DMRLogicalChannel)getMoveToChannel()).getLSNArray();
+ return dmrLsn.getLogicalSlotNumbers();
}
return new int[0];
@@ -241,13 +241,13 @@ public int[] getLogicalTimeslotNumbers()
@Override
public void apply(List timeslotFrequencies)
{
- if(getMoveToChannel() instanceof DMRLogicalChannel)
+ if(getMoveToChannel() instanceof DMRTier3Channel)
{
- DMRLogicalChannel channel = (DMRLogicalChannel)getMoveToChannel();
+ DMRTier3Channel channel = (DMRTier3Channel)getMoveToChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
- if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
+ if(channel.getValue() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java
index 3905a3f25..73121bdec 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AdjacentSiteInformation.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,24 +23,20 @@
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
-import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
-import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
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.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
-
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Announcement Message - Adjacent/Neighbor Site Information
*/
-public class AdjacentSiteInformation extends Announcement implements ITimeslotFrequencyReceiver
+public class AdjacentSiteInformation extends Announcement
{
private static final int NEIGHBOR_SYSTEM_IDENTITY_CODE_OFFSET = 21;
private static final int NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG = 56;
@@ -235,39 +231,4 @@ public List getIdentifiers()
return mIdentifiers;
}
-
- /**
- * Logical Slot Number(s) for channels contained in this message
- */
- @Override
- public int[] getLogicalTimeslotNumbers()
- {
- if(getNeighborChannel() instanceof DMRLogicalChannel)
- {
- return ((DMRLogicalChannel)getNeighborChannel()).getLSNArray();
- }
-
- return new int[0];
- }
-
- /**
- * Applies the timeslot frequency lookup information to channels contained in this message
- * @param timeslotFrequencies that match the logical timeslots
- */
- @Override
- public void apply(List timeslotFrequencies)
- {
- if(getNeighborChannel() instanceof DMRLogicalChannel)
- {
- DMRLogicalChannel channel = (DMRLogicalChannel)getNeighborChannel();
-
- for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
- {
- if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
- {
- channel.setTimeslotFrequency(timeslotFrequency);
- }
- }
- }
- }
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java
index 4dd2ca297..b1674fde3 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/AnnounceWithdrawTSCC.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
@@ -29,7 +29,6 @@
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
-
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java
index 96ad4d92a..c0c5c6dab 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/announcement/VoteNowAdvice.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,7 @@
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -33,7 +33,6 @@
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
-
import java.util.ArrayList;
import java.util.List;
@@ -240,11 +239,11 @@ public List getIdentifiers()
* Logical Slot Number(s) for channels contained in this message
*/
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- if(getChannel() instanceof DMRLogicalChannel)
+ if(getChannel() instanceof DMRLsn dmrLsn)
{
- return ((DMRLogicalChannel)getChannel()).getLSNArray();
+ return dmrLsn.getLogicalSlotNumbers();
}
return new int[0];
@@ -257,13 +256,13 @@ public int[] getLogicalTimeslotNumbers()
@Override
public void apply(List timeslotFrequencies)
{
- if(getChannel() instanceof DMRLogicalChannel)
+ if(getChannel() instanceof DMRTier3Channel)
{
- DMRLogicalChannel channel = (DMRLogicalChannel)getChannel();
+ DMRTier3Channel channel = (DMRTier3Channel)getChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
- if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
+ if(channel.getValue() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java
index 47e54a3a2..6229430e4 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/standard/grant/ChannelGrant.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
@@ -22,7 +22,7 @@
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
@@ -32,7 +32,6 @@
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
-
import java.util.List;
/**
@@ -159,11 +158,11 @@ public DMRChannel getChannel()
* Logical Slot Number(s) for channels contained in this message
*/
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- if(getChannel() instanceof DMRLogicalChannel)
+ if(getChannel() instanceof DMRLsn dmrLsn)
{
- return ((DMRLogicalChannel)getChannel()).getLSNArray();
+ return dmrLsn.getLogicalSlotNumbers();
}
return new int[0];
@@ -176,13 +175,13 @@ public int[] getLogicalTimeslotNumbers()
@Override
public void apply(List timeslotFrequencies)
{
- if(getChannel() instanceof DMRLogicalChannel)
+ if(getChannel() instanceof DMRTier3Channel)
{
- DMRLogicalChannel channel = (DMRLogicalChannel)getChannel();
+ DMRTier3Channel channel = (DMRTier3Channel)getChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
- if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
+ if(channel.getValue() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java
index 89828448b..893b5fd5c 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/DefinedShortDataHeader.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
@@ -25,7 +25,6 @@
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.type.DefinedDataFormat;
-
import java.util.ArrayList;
import java.util.List;
@@ -39,6 +38,7 @@ public class DefinedShortDataHeader extends PacketSequenceHeader
private static final int RESYNCHRONIZE_FLAG = 70;
private static final int FULL_MESSAGE_FLAG = 71;
private static final int[] BIT_PADDING = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
+ private static final int[] HEADER_CRC = new int[]{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95};
private List mIdentifiers;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java
index 2f40f045f..33b8eef1d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/ProprietaryDataHeader.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
@@ -34,6 +34,11 @@ public class ProprietaryDataHeader extends DataHeader
{
private static final int[] SERVICE_ACCESS_POINT = new int[]{0, 1, 2, 3};
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
+ private static final int[] VENDOR_DATA = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 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, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
+ private static final int[] HEADER_CRC = new int[]{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95};
/**
* Constructs an instance.
@@ -62,10 +67,20 @@ public String toString()
sb.append(" PROPRIETARY DATA HEADER");
sb.append(" VENDOR:").append(getVendor());
sb.append(" ").append(getServiceAccessPoint());
+ sb.append(" VENDOR DATA:").append(getVendorData());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
+ /**
+ * Vendor defined data payload from this proprietary header.
+ * @return hex values.
+ */
+ public String getVendorData()
+ {
+ return getMessage().getHex(VENDOR_DATA, 16);
+ }
+
/**
* Optional packet prefix.
* @return packet prefix fragment or null.
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java
index 523b56835..b3346d518 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MNISProprietaryDataHeader.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -66,7 +66,8 @@ public MNISProprietaryDataHeader(DMRSyncPattern syncPattern, CorrectedBinaryMess
public String toString()
{
StringBuilder sb = new StringBuilder();
- sb.append("MOTOROLA MNIS HEADER");
+ sb.append("CC:").append(getSlotType().getColorCode());
+ sb.append(" MOTOROLA MNIS HEADER");
if(getApplicationType() == ApplicationType.UNKNOWN)
{
sb.append(" APPLICATION TYPE:0x").append(Integer.toHexString(getApplicationTypeNumber()).toUpperCase());
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaProprietaryDataHeader.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java
similarity index 62%
rename from src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaProprietaryDataHeader.java
rename to src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.java
index 4e0070df9..0bad828a2 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaProprietaryDataHeader.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/header/motorola/MotorolaDataEncryptionHeader.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
@@ -31,10 +31,15 @@
/**
* Motorola Proprietary Data Header
*/
-public class MotorolaProprietaryDataHeader extends ProprietaryDataHeader
+public class MotorolaDataEncryptionHeader extends ProprietaryDataHeader
{
private static final int[] SERVICE_ACCESS_POINT = new int[]{0, 1, 2, 3};
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
+ private static final int[] ALGORITHM = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
+ private static final int[] KEY_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
+ private static final int[] UNKNOWN = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
+ private static final int[] INITIALIZATION_VECTOR = 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, 72, 73, 74, 75, 76, 77, 78, 79};
/**
* Constructs an instance.
@@ -46,7 +51,7 @@ public class MotorolaProprietaryDataHeader extends ProprietaryDataHeader
* @param timestamp message was received
* @param timeslot for the DMR burst
*/
- public MotorolaProprietaryDataHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
+ public MotorolaDataEncryptionHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@@ -55,14 +60,57 @@ public MotorolaProprietaryDataHeader(DMRSyncPattern syncPattern, CorrectedBinary
public String toString()
{
StringBuilder sb = new StringBuilder();
- sb.append("MOTOROLA PROPRIETARY DATA HEADER");
- sb.append(" ").append(getServiceAccessPoint());
+ sb.append("CC:").append(getSlotType().getColorCode());
+ 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(" UNK:").append(getUnknown());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
+ /**
+ * Unknown message field(s).
+ * @return hex value.
+ */
+ public String getUnknown()
+ {
+ return getMessage().getHex(UNKNOWN, 4);
+ }
+
+ /**
+ * Encryption key ID
+ * @return key ID
+ */
+ public int getKeyId()
+ {
+ return getMessage().getInt(KEY_ID);
+ }
+
+ /**
+ * Encryption Algorithm
+ * @return algorithm ID
+ */
+ public int getAlgorithm()
+ {
+ return getMessage().getInt(ALGORITHM);
+ }
+
+ /**
+ * Encryption initialization vector
+ *
+ * @return vector in hex
+ */
+ public String getInitializationVector()
+ {
+ return getMessage().getHex(INITIALIZATION_VECTOR, 8);
+ }
+
/**
* Utility method to lookup the vendor from a CSBK message
+ *
* @param message containing CSBK bits
* @return vendor
*/
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 62736700d..ba5d20da8 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
@@ -37,8 +37,9 @@
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.CapacityPlusGroupVoiceChannelUser;
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.MotorolaEncryptionParameters;
+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;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusControlChannel;
@@ -113,7 +114,7 @@ else if(message.size() == 96)
flc = new TerminatorData(message, timestamp, timeslot);
break;
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
- flc = new CapacityPlusGroupVoiceChannelUser(message, timestamp, timeslot);
+ flc = new MotorolaGroupVoiceChannelUser(message, timestamp, timeslot);
break;
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
@@ -121,7 +122,9 @@ else if(message.size() == 96)
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
flc = new CapacityPlusWideAreaVoiceChannelUser(message, timestamp, timeslot);
break;
-
+ case FULL_CAPACITY_PLUS_ENCRYPTION_PARAMETERS:
+ flc = new MotorolaEncryptionParameters(message, timestamp, timeslot);
+ break;
case FULL_HYTERA_GROUP_VOICE_CHANNEL_USER:
flc = new HyteraGroupVoiceChannelUser(message, timestamp, timeslot);
break;
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 a35dab54c..5fc477a3e 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
@@ -48,6 +48,8 @@ public enum LCOpcode
FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"),
//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_CAPACITY_PLUS_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)
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/AbstractVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/AbstractVoiceChannelUser.java
index 635bd5310..b4d78c962 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/AbstractVoiceChannelUser.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/AbstractVoiceChannelUser.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
@@ -20,6 +20,7 @@
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
+import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
/**
@@ -27,7 +28,7 @@
*
* ETSI TS 102 361-2 7.1.1.2
*/
-public abstract class AbstractVoiceChannelUser extends FullLCMessage
+public abstract class AbstractVoiceChannelUser extends FullLCMessage implements IServiceOptionsProvider
{
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
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 8d40334d3..2c7936543 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
@@ -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
@@ -22,13 +22,14 @@
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
+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.type.ServiceOptions;
/**
* Hytera Full Link Control
*/
-public abstract class HyteraFullLC extends FullLCMessage
+public abstract class HyteraFullLC extends FullLCMessage implements IServiceOptionsProvider
{
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] FREE_REPEATER = new int[]{24, 25, 26, 27};
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusEncryptedVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusEncryptedVoiceChannelUser.java
index b3917c03f..f822d573f 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusEncryptedVoiceChannelUser.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusEncryptedVoiceChannelUser.java
@@ -37,7 +37,7 @@ public class CapacityPlusEncryptedVoiceChannelUser extends CapacityPlusVoiceChan
private 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[] UNKNOWN_2 = 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_3 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
+ //Reed Solomon FEC: 72-95
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
@@ -94,10 +94,6 @@ public String getUnknown2()
{
return getMessage().getHex(UNKNOWN_2, 2);
}
- public String getUnknown3()
- {
- return getMessage().getHex(UNKNOWN_3, 2);
- }
/**
* Source radio address
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusGroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusGroupVoiceChannelUser.java
deleted file mode 100644
index c496aa452..000000000
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusGroupVoiceChannelUser.java
+++ /dev/null
@@ -1,236 +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.identifier.radio.RadioIdentifier;
-import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
-import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
-import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
-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;
-
-/**
- * Motorola Capacity Plus - Group Voice Channel User
- */
-public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
-{
- private static final int[] CAPACITY_PLUS_GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
- private static final int[] CONVENTIONAL_GROUP_ADDRESS = 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[] REST_CHANNEL = new int[]{51, 52, 53, 54};
- private static final int[] REST_CHANNEL_TIMESLOT = new int[]{55};
- private static final int[] CAPACITY_PLUS_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
- private static final int[] CONVENTIONAL_SOURCE_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
- 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
- private static final int[] UNKNOWN = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
-
- private RadioIdentifier mRadio;
- private TalkgroupIdentifier mTalkgroup;
- private DMRLogicalChannel mRestChannel;
- private List mIdentifiers;
-
- /**
- * Constructs an instance.
- *
- * @param message for the link control payload
- */
- public CapacityPlusGroupVoiceChannelUser(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");
- }
-
- if(getServiceOptions().isCapacityPlus())
- {
- sb.append("FLC MOTOROLA CAP+ GROUP VOICE CHANNEL USER");
- }
- else
- {
- sb.append("FLC MOTOROLA CONV/IP SITE GROUP VOICE CHANNEL USER");
- }
- sb.append(" FM:").append(getRadio());
- sb.append(" TO:").append(getTalkgroup());
-
- if(hasRestChannel())
- {
- sb.append(" REST:");
- sb.append(getRestChannel());
- }
-
- sb.append(" ").append(getServiceOptions());
- sb.append(" MSG:").append(getMessage().toHexString());
- return sb.toString();
- }
-
- /**
- * Unknown 8-bit field
- */
- public String getUnknown()
- {
- return getMessage().getHex(UNKNOWN, 2);
- }
-
- /**
- * Logical channel number (ie repeater number).
- */
- public DMRLogicalChannel getRestChannel()
- {
- if(mRestChannel == null)
- {
- mRestChannel = new DMRLogicalChannel(getRestChannelRepeater(), getRestChannelTimeslot());
- }
-
- return mRestChannel;
- }
-
- /**
- * Rest repeater number
- */
- public int getRestChannelRepeater()
- {
- return getMessage().getInt(REST_CHANNEL) + 1;
- }
-
- /**
- * Rest timeslot
- *
- * @return
- */
- public int getRestChannelTimeslot()
- {
- return getMessage().getInt(REST_CHANNEL_TIMESLOT) + 1;
- }
-
- /**
- * Indicates if this message has a rest channel indicated for the call
- */
- public boolean hasRestChannel()
- {
- return getServiceOptions().isCapacityPlus() && getRestChannelRepeater() != 0;
- }
-
- /**
- * Source radio address
- */
- public RadioIdentifier getRadio()
- {
- if(mRadio == null)
- {
- if(getServiceOptions().isCapacityPlus())
- {
- mRadio = DMRRadio.createFrom(getMessage().getInt(CAPACITY_PLUS_SOURCE_ADDRESS));
- }
- else
- {
- mRadio = DMRRadio.createFrom(getMessage().getInt(CONVENTIONAL_SOURCE_ADDRESS));
- }
- }
-
- return mRadio;
- }
-
- /**
- * Talkgroup address
- */
- public TalkgroupIdentifier getTalkgroup()
- {
- if(mTalkgroup == null)
- {
- if(getServiceOptions().isCapacityPlus())
- {
- mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CAPACITY_PLUS_GROUP_ADDRESS));
- }
- else
- {
- mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CONVENTIONAL_GROUP_ADDRESS));
- }
- }
-
- return mTalkgroup;
- }
-
- @Override
- public List getIdentifiers()
- {
- if(mIdentifiers == null)
- {
- mIdentifiers = new ArrayList<>();
- mIdentifiers.add(getTalkgroup());
- mIdentifiers.add(getRadio());
-
- if(hasRestChannel())
- {
- mIdentifiers.add(getRestChannel());
- }
- }
-
- return mIdentifiers;
- }
-
- /**
- * Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
- */
- @Override
- public int[] getLogicalTimeslotNumbers()
- {
- return getRestChannel().getLSNArray();
- }
-
- /**
- * Applies the LSN to frequency map to the rest channel.
- *
- * @param timeslotFrequencies that match the logical timeslots
- */
- @Override
- public void apply(List timeslotFrequencies)
- {
- for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
- {
- if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
- {
- getRestChannel().setTimeslotFrequency(timeslotFrequency);
- }
- }
- }
-}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusVoiceChannelUser.java
index 9c80a9e89..2a8da6c51 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusVoiceChannelUser.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusVoiceChannelUser.java
@@ -20,15 +20,17 @@
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
+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.type.CapacityPlusServiceOptions;
/**
* Any Capacity Plus Voice Channel User link control message that contains vendor-specific service options.
*/
-public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage
+public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage implements IServiceOptionsProvider
{
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
+ //Reed Solomon FEC: 72-95
private CapacityPlusServiceOptions mServiceOptions;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusWideAreaVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusWideAreaVoiceChannelUser.java
index 361093b6b..f44af10e4 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusWideAreaVoiceChannelUser.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityPlusWideAreaVoiceChannelUser.java
@@ -23,7 +23,7 @@
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.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
@@ -32,21 +32,23 @@
import java.util.List;
/**
- * Motorola Capacity Plus - Wide Area (Multi-Site) Voice Channel User
+ * Motorola Capacity Plus - Wide Area (Linked or Multi-Site) Voice Channel User
+ *
+ * Note: in Linked Capacity Plus talkgroups range 1-255 and radio IDs range 1-65535
*/
public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
{
- private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
- private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
- //private static final int[] REST_REPEATER = new int[]{51, 52, 53, 54};
- //private static final int[] REST_TIMESLOT = new int[]{55};
- private static final int[] REST_LSN = new int[]{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_3 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
+ //Bits 16-23: Service Options
+ private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
+ private static final int[] GROUP_ID = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
+ private static final int[] UNUSED = new int[]{48, 49, 50};
+ private static final int[] REST_LSN = new int[]{51, 52, 53, 54, 55};
+ private static final int[] RADIO_ID = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
+ //Reed Solomon FEC: 72-95
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
- private DMRLogicalChannel mRestChannel;
+ private DmrRestLsn mRestChannel;
private List mIdentifiers;
/**
@@ -82,18 +84,12 @@ public String toString()
sb.append("FLC MOTOROLA CAP+ WIDE-AREA VOICE CHANNEL USER FM:");
sb.append(getRadio());
sb.append(" TO:").append(getTalkgroup());
- sb.append(" REST:");
if(hasRestChannel())
{
- sb.append(getRestChannel());
- }
- else
- {
- sb.append("--");
+ sb.append(" ").append(getRestChannel());
}
- sb.append(" UNK1:").append(getUnknown1());
- sb.append(" UNK2:").append(getUnknown2());
sb.append(" ").append(getServiceOptions());
+ sb.append(" UNK1:").append(getUnknown1());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@@ -101,27 +97,19 @@ public String toString()
/**
* Unknown1 8-bit field
*/
- public int getUnknown1()
- {
- return getMessage().getInt(UNKNOWN_1);
- }
-
- /**
- * Unknown2 8-bit field
- */
- public int getUnknown2()
+ public String getUnknown1()
{
- return getMessage().getInt(UNKNOWN_3);
+ return getMessage().getHex(UNKNOWN_1, 4);
}
/**
* Logical channel number (ie repeater number).
*/
- public DMRLogicalChannel getRestChannel()
+ public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
- mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
+ mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
@@ -136,25 +124,12 @@ public int getRestLSN()
return getMessage().getInt(REST_LSN);
}
- /**
- * Rest channel timeslot
- */
- public int getRestTimeslot()
- {
- return (getRestLSN() % 2 == 0) ? 2 : 1;
- }
-
- /**
- * Rest channel repeater number
- */
- public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
-
/**
* Indicates if this message has a reset channel defined.
*/
public boolean hasRestChannel()
{
- return getRestRepeater() != 0;
+ return getRestLSN() != 0;
}
/**
@@ -164,7 +139,7 @@ public RadioIdentifier getRadio()
{
if(mRadio == null)
{
- mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
+ mRadio = DMRRadio.createFrom(getMessage().getInt(RADIO_ID));
}
return mRadio;
@@ -177,7 +152,7 @@ public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
- mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
+ mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ID));
}
return mTalkgroup;
@@ -201,9 +176,9 @@ public List getIdentifiers()
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
*/
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- return getRestChannel().getLSNArray();
+ return getRestChannel().getLogicalSlotNumbers();
}
/**
@@ -216,7 +191,7 @@ public void apply(List timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
- if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
+ if(getRestChannel().getValue() == timeslotFrequency.getNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaEncryptionParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaEncryptionParameters.java
new file mode 100644
index 000000000..ae5d53188
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaEncryptionParameters.java
@@ -0,0 +1,128 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2023 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.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 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 MotorolaEncryptionParameters 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[] ALGORITHM = new int[]{56, 57, 58, 59, 60, 61, 62, 63};
+ private static final int[] DESTINATION_GROUP = new int[]{64, 65, 66, 67, 68, 69, 70, 71};
+ //Reed Solomon FEC: 72-95
+
+ private DMRTalkgroup mTalkgroup;
+ private List mIdentifiers;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param message for the link control payload
+ */
+ public MotorolaEncryptionParameters(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 ENCRYPTION PARAMETERS - ALGORITHM:").append(getAlgorithm());
+ 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 getAlgorithm()
+ {
+ int algorithm = getMessage().getInt(ALGORITHM);
+
+ if(algorithm == 0)
+ {
+ return "EP/ARC4";
+ }
+
+ return "UNK(" + algorithm + ")";
+ }
+
+ 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/lc/full/motorola/MotorolaGroupVoiceChannelUser.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaGroupVoiceChannelUser.java
new file mode 100644
index 000000000..4c5ecf491
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/MotorolaGroupVoiceChannelUser.java
@@ -0,0 +1,122 @@
+/*
+ * *****************************************************************************
+ * 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.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;
+
+/**
+ * Motorola Capacity Plus - Group Voice Channel User
+ */
+public class MotorolaGroupVoiceChannelUser extends CapacityPlusVoiceChannelUser
+{
+ private static final int[] GROUP_ADDRESS = 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_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
+ //Reed Solomon FEC: 72-95
+
+ private RadioIdentifier mRadio;
+ private TalkgroupIdentifier mTalkgroup;
+ private List mIdentifiers;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param message for the link control payload
+ */
+ public MotorolaGroupVoiceChannelUser(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 GROUP VOICE CHANNEL USER");
+ sb.append(" FM:").append(getRadio());
+ sb.append(" TO:").append(getTalkgroup());
+ sb.append(" ").append(getServiceOptions());
+ sb.append(" MSG:").append(getMessage().toHexString());
+ return sb.toString();
+ }
+
+ /**
+ * Source radio address
+ */
+ public RadioIdentifier getRadio()
+ {
+ if(mRadio == null)
+ {
+ mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
+ }
+
+ return mRadio;
+ }
+
+ /**
+ * Talkgroup address
+ */
+ public TalkgroupIdentifier getTalkgroup()
+ {
+ if(mTalkgroup == null)
+ {
+ mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
+ }
+
+ return mTalkgroup;
+ }
+
+ @Override
+ public List getIdentifiers()
+ {
+ if(mIdentifiers == null)
+ {
+ mIdentifiers = new ArrayList<>();
+ mIdentifiers.add(getTalkgroup());
+ mIdentifiers.add(getRadio());
+ }
+
+ return mIdentifiers;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ActivityUpdateMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ActivityUpdateMessage.java
index 1c1763c42..8eb7fa64d 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ActivityUpdateMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ActivityUpdateMessage.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
@@ -22,7 +22,6 @@
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.message.type.Activity;
-
import java.util.Collections;
import java.util.List;
@@ -55,13 +54,18 @@ public String toString()
{
sb.append("[CRC ERROR] ");
}
-
- sb.append("SLC ACTIVITY UPDATE TS1 [");
- sb.append(getHashAddressTS1()).append("] ");
+ sb.append("SLC TS1:");
sb.append(getActivityTS1());
- sb.append(" / TS2 [");
- sb.append(getHashAddressTS2()).append("] ");
+ if(getActivityTS1() != Activity.IDLE)
+ {
+ sb.append(" [").append(getHashAddressTS1()).append("]");
+ }
+ sb.append(" TS2:");
sb.append(getActivityTS2());
+ if(getActivityTS2() != Activity.IDLE)
+ {
+ sb.append(" [").append(getHashAddressTS2()).append("]");
+ }
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/CapacityPlusRestChannel.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/CapacityPlusRestChannel.java
index f8896947c..b3e3e893b 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/CapacityPlusRestChannel.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/CapacityPlusRestChannel.java
@@ -21,7 +21,7 @@
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
+import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
@@ -34,13 +34,11 @@
public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslotFrequencyReceiver
{
private static final int[] UNKNOWN = new int[]{12, 13, 14};
- //private static final int[] REST_REPEATER = new int[]{15, 16, 17, 18};
private static final int[] REST_LSN = new int[]{15, 16, 17, 18, 19};
- //private static final int[] REST_TIMESLOT = new int[]{19};
private static final int[] SITE = new int[]{20, 21, 22, 23, 24};
private static final int[] UNKNOWN_2 = new int[]{25, 26, 27};
- private DMRLogicalChannel mRestChannel;
+ private DmrRestLsn mRestChannel;
private DMRSite mSite;
private List mIdentifiers;
@@ -63,7 +61,7 @@ public String toString()
sb.append("[CRC ERROR] ");
}
sb.append("SLC MOTOROLA CAP+ SITE:").append(getSite());
- sb.append(" REST:").append(getRestChannel());
+ sb.append(" ").append(getRestChannel());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@@ -84,11 +82,11 @@ public DMRSite getSite()
/**
* Rest Channel Number
*/
- public DMRLogicalChannel getRestChannel()
+ public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
- mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
+ mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
@@ -103,26 +101,13 @@ public int getRestLSN()
return getMessage().getInt(REST_LSN);
}
- /**
- * Rest repeater
- */
- public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
-
- /**
- * Rest timeslot
- */
- public int getRestTimeslot()
- {
- return (getRestLSN() % 2 == 0) ? 2 : 1;
- }
-
/**
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
*/
@Override
- public int[] getLogicalTimeslotNumbers()
+ public int[] getLogicalSlotNumbers()
{
- return getRestChannel().getLSNArray();
+ return getRestChannel().getLogicalSlotNumbers();
}
/**
@@ -134,7 +119,7 @@ public void apply(List timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
- if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
+ if(getRestChannel().getValue() == timeslotFrequency.getNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/NullMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/NullMessage.java
index 8a18ce85f..ef96de9fe 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/NullMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/NullMessage.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,7 +21,6 @@
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
-
import java.util.Collections;
import java.util.List;
@@ -48,8 +47,7 @@ public String toString()
{
sb.append("[CRC ERROR] ");
}
- sb.append("SLC IDLE/NULL MESSAGE");
- sb.append(" MSG:").append(getMessage().toHexString());
+ sb.append("SLC TS1:IDLE TS2:IDLE");
return sb.toString();
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/DMRPacketMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/DMRPacketMessage.java
index 063295a90..6d7dfcb62 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/DMRPacketMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/packet/DMRPacketMessage.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.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
import io.github.dsheirer.module.decode.ip.IPacket;
-
import java.util.ArrayList;
import java.util.List;
@@ -75,6 +74,10 @@ public String toString()
sb.append("CC:").append(getPacketSequence().getPacketSequenceHeader().getSlotType().getColorCode());
sb.append(" FM:").append(getPacketSequence().getPacketSequenceHeader().getSourceLLID());
sb.append(" TO:").append(getPacketSequence().getPacketSequenceHeader().getDestinationLLID());
+ if(getPacketSequence().isEncrypted())
+ {
+ sb.append(" ENCRYPTED");
+ }
sb.append(" ").append(getPacket().toString());
return sb.toString();
}
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 9136b1519..d22bc6812 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,6 +24,7 @@
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.motorola.MotorolaDataEncryptionHeader;
import java.util.ArrayList;
import java.util.List;
@@ -89,6 +90,15 @@ public boolean isComplete()
return false;
}
+ /**
+ * Indicates if this sequence contains a Motorola data encryption header.
+ * @return true if so
+ */
+ public boolean isEncrypted()
+ {
+ return hasProprietaryDataHeader() && getProprietaryDataHeader() instanceof MotorolaDataEncryptionHeader;
+ }
+
public int getTimeslot()
{
return mTimeslot;
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 cb444b37d..4a27855c0 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
@@ -29,7 +29,10 @@
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.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.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;
@@ -179,9 +182,20 @@ else if(secondaryHeader instanceof HyteraProprietaryDataHeader)
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
}
+ else if(secondaryHeader instanceof MotorolaDataEncryptionHeader &&
+ packetSequence.getPacketSequenceHeader().getDataPacketFormat() == DataPacketFormat.DEFINED_SHORT_DATA)
+ {
+ return createDefinedShortData(packetSequence, packet);
+ }
else
{
- mLog.info("Unknown Proprietary Packet Header Type - creating unknown packet.");
+ if(packetSequence.getProprietaryDataHeader() != null)
+ {
+ mLog.info("Unknown Proprietary Packet Header Type - creating unknown packet. Data Packet Format: " +
+ packetSequence.getPacketSequenceHeader().getDataPacketFormat() + " Proprietary Header: " +
+ packetSequence.getProprietaryDataHeader().getClass());
+ }
+
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
@@ -217,8 +231,7 @@ public static IMessage createIPPacketData(PacketSequence packetSequence, Correct
*/
public static IMessage createDefinedShortData(PacketSequence packetSequence, CorrectedBinaryMessage packet)
{
- mLog.info("Unknown Short Data Packet Header Type - creating unknown packet.");
- return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
+ return new DMRPacketMessage(packetSequence, new DefinedShortDataPacket(packet, 0), packet,
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/CapacityPlusServiceOptions.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/CapacityPlusServiceOptions.java
index b9a13e158..84ddd55f1 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/CapacityPlusServiceOptions.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/type/CapacityPlusServiceOptions.java
@@ -83,7 +83,7 @@ public String toString()
if(isInterruptible())
{
- flags.add("TXI CALL");
+ flags.add("INTERRUPTIBLE CALL"); //Indicates another user can interrupt this call
}
if(getPriority() > 0)
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/EMB.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/EMB.java
index c721b668f..8e137e9fe 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/EMB.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/EMB.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,18 +21,16 @@
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
+import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.HashSet;
-
/**
* DMR Voice Frame B-F Embedded Signalling Chunk
*/
public class EMB
{
private final static Logger mLog = LoggerFactory.getLogger(EMB.class);
- public static final int[] CRC_CHECKSUMS = new int[]{0x02F, 0x11E, 0x1B7, 0x1E2, 0x1C9, 0x0E5, 0x073};
private static final int[] VALID_WORDS = new int[]{
0x0000, 0x0273, 0x04E5, 0x0696, 0x09C9, 0x0BBA, 0x0D2C, 0x0F5F, 0x11E2, 0x1391, 0x1507, 0x1774, 0x182B, 0x1A58,
0x1CCE, 0x1EBD, 0x21B7, 0x23C4, 0x2552, 0x2721, 0x287E, 0x2A0D, 0x2C9B, 0x2EE8, 0x3055, 0x3226, 0x34B0, 0x36C3,
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java
index aebe3c07b..4e83e99e1 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2022 Dennis Sheirer
+ * Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@
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.voice.embedded.EmbeddedParameters;
/**
* DMR Voice Frames B - F
@@ -35,6 +36,7 @@ public class VoiceEMBMessage extends VoiceMessage
private static final int PAYLOAD_END = 172;
private EMB mEMB;
+ private EmbeddedParameters mEmbeddedParameters;
/**
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
@@ -61,7 +63,11 @@ public String toString()
sb.append(getSyncPattern().toString());
- if(getEMB().isValid() && getEMB().isEncrypted())
+ if(hasEmbeddedParameters())
+ {
+ sb.append(" ").append(getEmbeddedParameters());
+ }
+ else if(getEMB().isValid() && getEMB().isEncrypted())
{
sb.append(" ENCRYPTED");
}
@@ -97,4 +103,34 @@ public BinaryMessage getFLCFragment()
{
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
}
+
+ /**
+ * Optional embedded parameters for this voice super-frame.
+ * @return encryption parameters or null.
+ */
+ public EmbeddedParameters getEmbeddedParameters()
+ {
+ return mEmbeddedParameters;
+ }
+
+ /**
+ * Sets the embedded parameters for this voice message that apply to the entire voice super-frame.
+ *
+ * These parameters are normally extracted by an external process and applied to voice frame F and the parameters
+ * apply to the entire voice super-frame.
+ * @param embeddedParameters to set
+ */
+ public void setEmbeddedParameters(EmbeddedParameters embeddedParameters)
+ {
+ mEmbeddedParameters = embeddedParameters;
+ }
+
+ /**
+ * Indicates if this voice message contains embedded parameters.
+ * @return true if it contains.
+ */
+ public boolean hasEmbeddedParameters()
+ {
+ return mEmbeddedParameters != null;
+ }
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java
index 1107ff73b..a092ab654 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceMessage.java
@@ -1,3 +1,22 @@
+/*
+ * *****************************************************************************
+ * 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.voice;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
@@ -5,7 +24,6 @@
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.DMRBurst;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -15,6 +33,14 @@
*/
public abstract class VoiceMessage extends DMRBurst
{
+ /**
+ * Initialization vector (IV) fragments can be embedded into the AMBE voice frame for late entry to encrypted calls
+ * using bits 71, 67, 63, and 59 from the interleaved (ie transmitted order) voice frame.
+ */
+ public static int[] FRAME_1_IV_FRAGMENT = new int[]{95, 91, 87, 83};
+ public static int[] FRAME_2_IV_FRAGMENT = new int[]{215, 211, 207, 203};
+ public static int[] FRAME_3_IV_FRAGMENT = new int[]{287, 283, 279, 275};
+
/**
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
* station frames transmit a Common Announcement Channel (CACH) in the 24-bit prefix, whereas Mobile inbound frames
@@ -65,6 +91,24 @@ public List getAMBEFrames()
return frames;
}
+ /**
+ * Extracts the four low-order bits from the deinterleaved C3 vector of each of the three AMBE frames that carry
+ * fragments of the initialization vector (IV) for encrypted calls. Since we don't have access to the deinterleaved
+ * voice frame here, we perform delinterleaving of the 4-bit nibble using the FRAME_x_IV_FRAGMENT constants.
+ *
+ * See patent: https://patents.google.com/patent/EP2347540B1/en
+ *
+ * @return a three-byte array with the low-order four bits from each frame's C3 vector stored in the low nibble.
+ */
+ public byte[] getIvFragments()
+ {
+ byte[] fragments = new byte[3];
+ fragments[0] = (byte)getMessage().getInt(FRAME_1_IV_FRAGMENT);
+ fragments[1] = (byte)getMessage().getInt(FRAME_2_IV_FRAGMENT);
+ fragments[2] = (byte)getMessage().getInt(FRAME_3_IV_FRAGMENT);
+ return fragments;
+ }
+
@Override
public boolean isValid()
{
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
new file mode 100644
index 000000000..db58f16dc
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceSuperFrameProcessor.java
@@ -0,0 +1,282 @@
+/*
+ * *****************************************************************************
+ * 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.voice;
+
+import io.github.dsheirer.bits.BinaryMessage;
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+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.NonStandardShortBurst;
+import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NullShortBurst;
+import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurst;
+import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurstOpcode;
+import io.github.dsheirer.module.decode.dmr.message.voice.embedded.TransmitInterrupt;
+import io.github.dsheirer.module.decode.dmr.message.voice.embedded.UnknownShortBurst;
+
+/**
+ * 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
+ */
+public class VoiceSuperFrameProcessor
+{
+ private static final int[] IV = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24, 25, 26, 27, 28, 29, 30, 31, 32,
+ 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;
+ private byte[] mFragmentD;
+ private byte[] mFragmentE;
+
+ /**
+ * Constructor.
+ */
+ public VoiceSuperFrameProcessor()
+ {
+ }
+
+ /**
+ * Indicates if this collector is in collecting mode.
+ *
+ * @return true if collecting.
+ */
+ public boolean isCollecting()
+ {
+ return mEncrypted;
+ }
+
+ /**
+ * Fully resets this collector
+ */
+ public void reset()
+ {
+ mEncrypted = false;
+ softReset();
+ }
+
+ private void softReset()
+ {
+ mAlgorithm = 0;
+ mKey = 0;
+ mFragmentA = null;
+ mFragmentB = null;
+ mFragmentC = null;
+ mFragmentD = null;
+ mFragmentE = null;
+ }
+
+ /**
+ * Indicates if the IV fragments from voice frames A-E have been collected and this is an encrypted call.
+ *
+ * @return true if the fragments are non-null.
+ */
+ private boolean isComplete()
+ {
+ return mEncrypted && mFragmentA != null && mFragmentB != null && mFragmentC != null && mFragmentD != null &&
+ mFragmentE != null;
+ }
+
+ /**
+ * Process full link control message to determine if the current call sequence is encrypted.
+ * @param flc to inspect
+ */
+ public void process(FullLCMessage flc)
+ {
+ if(flc instanceof IServiceOptionsProvider provider && provider.getServiceOptions().isEncrypted())
+ {
+ mEncrypted = true;
+ }
+ }
+
+ /**
+ * Processes the voice frame to extract the IV fragments and the encryption parameters.
+ *
+ * @param voiceMessage to process.
+ */
+ public void process(VoiceMessage voiceMessage)
+ {
+ if(voiceMessage instanceof VoiceEMBMessage voiceEMB)
+ {
+ boolean valid = voiceEMB.getEMB().isValid();
+ boolean encrypted = voiceEMB.getEMB().isEncrypted();
+
+ if(voiceEMB.getEMB().isValid() && voiceEMB.getEMB().isEncrypted())
+ {
+ mEncrypted = true;
+ }
+ }
+
+ switch(voiceMessage.getSyncPattern())
+ {
+ case BASE_STATION_VOICE:
+ mFragmentA = voiceMessage.getIvFragments();
+ break;
+ case BS_VOICE_FRAME_B:
+ mFragmentB = voiceMessage.getIvFragments();
+ break;
+ case BS_VOICE_FRAME_C:
+ mFragmentC = voiceMessage.getIvFragments();
+ break;
+ case BS_VOICE_FRAME_D:
+ mFragmentD = voiceMessage.getIvFragments();
+ break;
+ case BS_VOICE_FRAME_E:
+ mFragmentE = voiceMessage.getIvFragments();
+ break;
+ case BS_VOICE_FRAME_F:
+ if(voiceMessage instanceof VoiceEMBMessage voiceFrame6)
+ {
+ BinaryMessage frameFFragment = voiceFrame6.getFLCFragment();
+ ShortBurst shortBurst = extractShortBurst(frameFFragment);
+ EmbeddedParameters embeddedParameters = new EmbeddedParameters(shortBurst);
+
+ if(isComplete())
+ {
+ String iv = extractIV(voiceMessage.getIvFragments());
+ embeddedParameters.setIv(iv);
+ }
+
+ voiceFrame6.setEmbeddedParameters(embeddedParameters);
+ }
+ softReset();
+ break;
+ }
+ }
+
+ /**
+ * Processes the short burst from the Voice Frame F FLC fragment payload and combines with the optionally available
+ * 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)
+ {
+ CorrectedBinaryMessage decoded = BPTC_16_2.decodeShortBurst(new CorrectedBinaryMessage(frameFFragment));
+
+ if(decoded == null)
+ {
+ return new NonStandardShortBurst(BPTC_16_2.deinterleave(new CorrectedBinaryMessage(frameFFragment)));
+ }
+
+ ShortBurstOpcode opcode = ShortBurst.getOpcode(decoded);
+ return switch(opcode)
+ {
+ case NULL -> new NullShortBurst(decoded);
+ case ARC4_ENCRYPTION -> new Arc4EncryptionParameters(decoded);
+ case TXI_DELAY -> new TransmitInterrupt(decoded);
+ default -> new UnknownShortBurst(decoded);
+ };
+ }
+
+ /**
+ * Extracts the encryption initialization vector (IV) from the voice frame fragments, performs error detection and
+ * correction and if the error correction passes, returns the hex string version of the 32-bit IV.
+ *
+ * @param mFragmentF voice frame 6 IV fragments.
+ * @return 32-bit IV as hex string or null if the extraction process couldn't extract/correct the IV.
+ */
+ private String extractIV(byte[] mFragmentF)
+ {
+ CorrectedBinaryMessage reassembled = new CorrectedBinaryMessage(72);
+ reassembled.setByte(0, combine(mFragmentA[0], mFragmentB[0]));
+ reassembled.setByte(8, combine(mFragmentC[0], mFragmentD[0]));
+ reassembled.setByte(16, combine(mFragmentE[0], mFragmentF[0]));
+ reassembled.setByte(24, combine(mFragmentA[1], mFragmentB[1]));
+ reassembled.setByte(32, combine(mFragmentC[1], mFragmentD[1]));
+ reassembled.setByte(40, combine(mFragmentE[1], mFragmentF[1]));
+ reassembled.setByte(48, combine(mFragmentA[2], mFragmentB[2]));
+ reassembled.setByte(56, combine(mFragmentC[2], mFragmentD[2]));
+ reassembled.setByte(64, combine(mFragmentE[2], mFragmentF[2]));
+
+ int check1 = Golay24.checkAndCorrect(reassembled, 0);
+ int check2 = Golay24.checkAndCorrect(reassembled, 24);
+ int check3 = Golay24.checkAndCorrect(reassembled, 48);
+
+ if(check1 == 2 || check2 == 2 || check3 == 2)
+ {
+ return null;
+ }
+
+ int iv = reassembled.getInt(IV);
+ int checksum = reassembled.getInt(CRC4);
+
+ boolean passes = crc4(iv, checksum);
+
+ if(passes)
+ {
+ return String.format("%04X", iv);
+ }
+ else
+ {
+ return String.format("%04X", iv) + "(CRC-FAIL " + check1 + "/" + check2 + "/" + check3 + "/" +
+ reassembled.getCorrectedBitCount() + ")";
+ }
+ }
+
+ /**
+ * Combines the low nibble from the hi byte with the low nibble from the lo byte
+ *
+ * @param high byte containing a low order nibble
+ * @param low byte containing a low order nibble
+ * @return low order nibbles from hi and lo bytes combined.
+ */
+ private byte combine(byte high, byte low)
+ {
+ return (byte)(((high & 0xF) << 4) | (low & 0xF));
+ }
+
+ /**
+ * Calculates a CRC value from the polynomial: x4 + x1 + 1 (0x13) using an initial fill of 0xF.
+ * @param value to calculate CRC from
+ * @param crc to compare
+ * @return true if the calculated CRC from the value matches the crc argument value.
+ */
+ public static boolean crc4(int value, int crc)
+ {
+ long checksum = (value & 0x0FFFFFFFFl) << 4;
+ checksum ^= 0xF; //Initial fill
+ long polynomial = 0x013l << 31;
+ long checkBit = 0x1l << 35;
+
+ for(int x = 31; x >= 0; x--)
+ {
+ if((checksum & checkBit) == checkBit)
+ {
+ checksum ^= polynomial;
+ }
+ polynomial >>= 1;
+ checkBit >>= 1;
+ }
+
+ return (int)checksum == crc;
+ }
+}
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/Arc4EncryptionParameters.java
new file mode 100644
index 000000000..e4385b0ca
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/Arc4EncryptionParameters.java
@@ -0,0 +1,80 @@
+/*
+ * *****************************************************************************
+ * 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.voice.embedded;
+
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+
+/**
+ * Encryption parameters short burst payload.
+ */
+public class Arc4EncryptionParameters 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};
+
+ /**
+ * Constructor
+ *
+ * @param message containing the de-interleaved and error-corrected short burst payload.
+ */
+ public Arc4EncryptionParameters(CorrectedBinaryMessage message)
+ {
+ super(message);
+ }
+
+ /**
+ * Encryption key ID
+ * @return key ID
+ */
+ public int getKey()
+ {
+ return getMessage().getInt(KEY);
+ }
+
+ /**
+ * Encryption algorithm
+ * @return algorithm (0 - 7). 1 = ARC4
+ */
+ public int getAlgorithmValue()
+ {
+ return getMessage().getInt(ALGORITHM);
+ }
+
+ public String getAlgorithm()
+ {
+ int algorithm = getAlgorithmValue();
+
+ if(algorithm == 1)
+ {
+ return "EP/ARC4";
+ }
+
+ return "ALGORITHM:" + algorithm;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ENCRYPTION:").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/EmbeddedParameters.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EmbeddedParameters.java
new file mode 100644
index 000000000..e9557f462
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/EmbeddedParameters.java
@@ -0,0 +1,90 @@
+/*
+ * *****************************************************************************
+ * 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.voice.embedded;
+
+/**
+ * Parameters that are embedded in a DMR voice super frame.
+ * @param algorithm identifier
+ * @param key identifier
+ * @param iv initialization vector
+ */
+public class EmbeddedParameters
+{
+ private ShortBurst mShortBurst;
+ private String mIv;
+
+ /**
+ * Constructor
+ * @param shortBurst payload
+ * @param iv initialization vector extracted from DMR voice super-frame
+ */
+ public EmbeddedParameters(ShortBurst shortBurst)
+ {
+ mShortBurst = shortBurst;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getShortBurst());
+ if(hasIv())
+ {
+ sb.append(" IV:").append(getIv());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Short Burst payload message
+ * @return short burst
+ */
+ public ShortBurst getShortBurst()
+ {
+ return mShortBurst;
+ }
+
+ /**
+ * Optional initialization vector (IV) decoded from the voice super-frame
+ * @return iv
+ */
+ public String getIv()
+ {
+ return mIv;
+ }
+
+ /**
+ * Sets the optional IV value.
+ * @param iv to set
+ */
+ public void setIv(String iv)
+ {
+ mIv = iv;
+ }
+
+ /**
+ * Indicates if the optional IV is included.
+ * @return true if included
+ */
+ public boolean hasIv()
+ {
+ return mIv != null;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/NonStandardShortBurst.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/NonStandardShortBurst.java
new file mode 100644
index 000000000..dd75b0aba
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/NonStandardShortBurst.java
@@ -0,0 +1,44 @@
+/*
+ * *****************************************************************************
+ * 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.voice.embedded;
+
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+
+/**
+ * Short burst payload that doesn't pass the BPTC 16 fec check.
+ */
+public class NonStandardShortBurst extends ShortBurst
+{
+ /**
+ * Constructor
+ *
+ * @param message containing the delinterleaved and error-corrected short burst payload.
+ */
+ public NonStandardShortBurst(CorrectedBinaryMessage message)
+ {
+ super(message);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "NON-STANDARD SHORT BURST:" + getMessage().toHexString();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/NullShortBurst.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/NullShortBurst.java
new file mode 100644
index 000000000..6d2ba5b9b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/NullShortBurst.java
@@ -0,0 +1,45 @@
+/*
+ * *****************************************************************************
+ * 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.voice.embedded;
+
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+
+/**
+ * Null (empty) short burst
+ */
+public class NullShortBurst extends ShortBurst
+{
+ /**
+ * Constructor
+ *
+ * @param message containing the delinterleaved and error-corrected short burst payload.
+ */
+ public NullShortBurst(CorrectedBinaryMessage message)
+ {
+ super(message);
+ setValid(passesCRC3());
+ }
+
+ @Override
+ public String toString()
+ {
+ return "NULL SHORT BURST";
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/ShortBurst.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/ShortBurst.java
new file mode 100644
index 000000000..4d0991ba6
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/ShortBurst.java
@@ -0,0 +1,134 @@
+/*
+ * *****************************************************************************
+ * 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.voice.embedded;
+
+import io.github.dsheirer.bits.BinaryMessage;
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+
+/**
+ * Base class for DMR Voice Frame F Short Burst from the EMB payload.
+ */
+public abstract class ShortBurst
+{
+ private static final int[] CRC3 = new int[]{0, 1, 2};
+ private static final int[] OPCODE = new int[]{8, 9, 10};
+ private static final int[] FULL_MESSAGE = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+ private CorrectedBinaryMessage mMessage;
+ private boolean mValid = true;
+
+ /**
+ * Constructor
+ *
+ * @param message containing the delinterleaved and error-corrected short burst payload.
+ */
+ public ShortBurst(CorrectedBinaryMessage message)
+ {
+ mMessage = message;
+ }
+
+ /**
+ * Access the underlying message.
+ *
+ * @return message
+ */
+ public CorrectedBinaryMessage getMessage()
+ {
+ return mMessage;
+ }
+
+ /**
+ * Indicates if this message passes any CRC checks.
+ * @return
+ */
+ public boolean isValid()
+ {
+ return mValid;
+ }
+
+ /**
+ * Sets the valid CRC flag for this message
+ * @param valid true or false
+ */
+ protected void setValid(boolean valid)
+ {
+ mValid = valid;
+ }
+
+ /**
+ * Numeric opcode value.
+ *
+ * @return value.
+ */
+ public int getOpcodeValue()
+ {
+ return getMessage().getInt(OPCODE);
+ }
+
+ /**
+ * Opcode for this message.
+ *
+ * @return opcode
+ */
+ public ShortBurstOpcode getOpcode()
+ {
+ return getOpcode(getMessage());
+ }
+
+ /**
+ * Static utility method to lookup the opcode from a short burst message.
+ *
+ * @param message containing a short burst
+ * @return opcode
+ */
+ public static ShortBurstOpcode getOpcode(BinaryMessage message)
+ {
+ return ShortBurstOpcode.fromValue(message.getInt(OPCODE));
+ }
+
+ /**
+ * Checks the message to determine if it passes for a CRC3. Not all Short Burst messages use the CRC3 check.
+ *
+ * @return true if it passes.
+ */
+ public boolean passesCRC3()
+ {
+ int checksum = getMessage().getInt(FULL_MESSAGE);
+ int polynomial = 0xB << 7;
+ int checkBit = 0x1 << 10;
+
+ for(int x = 10; x >= 0; x--)
+ {
+ if(checksum == 0)
+ {
+ return true;
+ }
+
+ if((checksum & checkBit) == checkBit)
+ {
+ checksum ^= polynomial;
+ }
+ polynomial >>= 1;
+ checkBit >>= 1;
+ }
+
+ return checksum == 0;
+ }
+}
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
new file mode 100644
index 000000000..8f9da8872
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/ShortBurstOpcode.java
@@ -0,0 +1,71 @@
+/*
+ * *****************************************************************************
+ * 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.voice.embedded;
+
+/**
+ * Short Burst opcode
+ *
+ * See: https://patents.google.com/patent/US8271009B2 Page 12
+ */
+public enum ShortBurstOpcode
+{
+ NULL(0),
+ ARC4_ENCRYPTION(1),
+ TXI_DELAY(3),
+ UNKNOWN(-1);
+
+ private int mValue;
+
+ /**
+ * Constructor
+ * @param value of the opcode
+ */
+ ShortBurstOpcode(int value)
+ {
+ mValue = value;
+ }
+
+ /**
+ * Numeric value for the opcode
+ * @return value.
+ */
+ private int getValue()
+ {
+ return mValue;
+ }
+
+ /**
+ * Lookup the enum entry from the specified value.
+ * @param value to lookup
+ * @return matching entry or UNKNOWN.
+ */
+ public static ShortBurstOpcode fromValue(int value)
+ {
+ for(ShortBurstOpcode opcode: ShortBurstOpcode.values())
+ {
+ if(opcode.getValue() == value)
+ {
+ return opcode;
+ }
+ }
+
+ return UNKNOWN;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/TransmitInterrupt.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/TransmitInterrupt.java
new file mode 100644
index 000000000..5685cf100
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/TransmitInterrupt.java
@@ -0,0 +1,74 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2023 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.module.decode.dmr.message.voice.embedded;
+
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+
+/**
+ * Transmit Interrupt short burst
+ *
+ * See: https://patents.google.com/patent/US8271009B2
+ */
+public class TransmitInterrupt extends ShortBurst
+{
+ private static final int[] DELAY = new int[]{3, 4, 5, 6, 7};
+
+ /**
+ * Constructor
+ *
+ * @param message containing the delinterleaved and error-corrected short burst payload.
+ */
+ public TransmitInterrupt(CorrectedBinaryMessage message)
+ {
+ super(message);
+ setValid(passesCRC3());
+ }
+
+ @Override
+ public String toString()
+ {
+ return "TRANSMIT INTERRUPT (TXI) AT " + getDelay();
+ }
+
+ /**
+ * Delay to when another radio can interrupt the current call.
+ * @return delay string.
+ */
+ public String getDelay()
+ {
+ int value = getMessage().getInt(DELAY);
+
+ switch(value)
+ {
+ case 0:
+ return "ANY TIME";
+ case 2:
+ return "FRAME E";
+ case 4:
+ return "FRAME D";
+ case 6:
+ return "FRAME C";
+ case 8:
+ return "FRAME B";
+ default:
+ return "UNKNOWN(" + value + ")";
+ }
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/UnknownShortBurst.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/UnknownShortBurst.java
new file mode 100644
index 000000000..8aa2925ea
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/embedded/UnknownShortBurst.java
@@ -0,0 +1,44 @@
+/*
+ * *****************************************************************************
+ * 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.voice.embedded;
+
+import io.github.dsheirer.bits.CorrectedBinaryMessage;
+
+/**
+ * Unknown or garbage short burst
+ */
+public class UnknownShortBurst extends ShortBurst
+{
+ /**
+ * Constructor
+ *
+ * @param message containing the delinterleaved and error-corrected short burst payload.
+ */
+ public UnknownShortBurst(CorrectedBinaryMessage message)
+ {
+ super(message);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "UNKNOWN SHORT BURST:" + getMessage().toHexString();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/ip/DefinedShortDataPacket.java b/src/main/java/io/github/dsheirer/module/decode/ip/DefinedShortDataPacket.java
new file mode 100644
index 000000000..a89ba9c15
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/ip/DefinedShortDataPacket.java
@@ -0,0 +1,98 @@
+/*
+ * *****************************************************************************
+ * 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;
+
+import io.github.dsheirer.bits.BinaryMessage;
+import io.github.dsheirer.identifier.Identifier;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Defined Short Data Packet
+ */
+public class DefinedShortDataPacket implements IPacket
+{
+ private BinaryMessage mMessage;
+ private int mOffset;
+
+ /**
+ * Constructor
+ * @param message of the complete packet
+ * @param offset into the message
+ */
+ public DefinedShortDataPacket(BinaryMessage message, int offset)
+ {
+ mMessage = message;
+ mOffset = offset;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("DEFINED SHORT DATA PACKET:");
+
+ if(getMessage().size() > getOffset())
+ {
+ sb.append(mMessage.getSubMessage(getOffset(), getMessage().size()).toHexString());
+ }
+ else
+ {
+ sb.append("(EMPTY)");
+ }
+
+ return sb.toString();
+ }
+
+ public BinaryMessage getMessage()
+ {
+ return mMessage;
+ }
+
+ public int getOffset()
+ {
+ return mOffset;
+ }
+
+ @Override
+ public IHeader getHeader()
+ {
+ return null;
+ }
+
+ @Override
+ public IPacket getPayload()
+ {
+ return null;
+ }
+
+ @Override
+ public boolean hasPayload()
+ {
+ return false;
+ }
+
+ @Override
+ public List getIdentifiers()
+ {
+ return Collections.emptyList();
+ }
+}