mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
#1618 Update Cap+ radio & group parsing, process TXI & voice frame encryption parameters, and many other changes to the DMR message processing.
This commit is contained in:
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal file
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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.
|
||||
* <p>
|
||||
* 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("-------------------------");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 class CRCUtil
|
||||
{
|
||||
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 poly = 0x13l;
|
||||
long[] checksums = generate(32, 4, poly, 0, true);
|
||||
|
||||
long polynomial = 0x11021l;
|
||||
decode(message, 0, 80, polynomial, 16);
|
||||
mLog.debug(message.toString());
|
||||
mLog.debug("Finished");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
@@ -5,27 +24,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
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 <http://www.gnu.org/licenses/>
|
||||
* -----------------------------------------------------------------------
|
||||
* 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 @@ public class Golay24
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,6 +459,12 @@ public class DMRRecordingViewer extends VBox
|
||||
if(mFindText == null)
|
||||
{
|
||||
mFindText = new TextField();
|
||||
mFindText.setOnKeyPressed(event -> {
|
||||
if(event.getCode().equals(KeyCode.ENTER))
|
||||
{
|
||||
getFindButton().fire();
|
||||
}
|
||||
});
|
||||
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
|
||||
}
|
||||
|
||||
|
||||
@@ -65,8 +65,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoice
|
||||
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 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
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 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
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 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
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 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
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 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
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 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -44,6 +44,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
|
||||
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<IMessage>
|
||||
{
|
||||
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 class DMRMessageProcessor implements Listener<IMessage>
|
||||
@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<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ public class DMRNetworkConfigurationMonitor
|
||||
*/
|
||||
private void addDmrChannel(DMRChannel dmrChannel)
|
||||
{
|
||||
mObservedChannelMap.put(dmrChannel.getLogicalSlotNumber(), dmrChannel);
|
||||
mObservedChannelMap.put(dmrChannel.getValue(), dmrChannel);
|
||||
}
|
||||
|
||||
public void reset()
|
||||
|
||||
@@ -322,7 +322,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
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);
|
||||
|
||||
@@ -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.GroupVoiceChann
|
||||
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 @@ public class DMRCallSequenceRecorder extends MBECallSequenceRecorder
|
||||
switch(message.getOpcode())
|
||||
{
|
||||
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
|
||||
if(message instanceof CapacityPlusGroupVoiceChannelUser cpvcu)
|
||||
if(message instanceof MotorolaGroupVoiceChannelUser cpvcu)
|
||||
{
|
||||
if(mCallSequence == null)
|
||||
{
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.channel;
|
||||
|
||||
@@ -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 abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
||||
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 abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "CHAN:" + getChannel() + ":" + getTimeslot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not implemented
|
||||
*/
|
||||
|
||||
@@ -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(channel, timeslot);
|
||||
super(((lsn - 1) / 2) + 1, ((lsn - 1) % 2) + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
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 class DMRLogicalChannel extends DMRChannel
|
||||
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 class DMRLogicalChannel extends DMRChannel
|
||||
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<TimeslotFrequency> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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 class DMRTier3Channel extends DMRLogicalChannel
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(repeater > 0)
|
||||
if(mTimeslotFrequency != null)
|
||||
{
|
||||
return ((repeater) * 2) + getTimeslot();
|
||||
return mTimeslotFrequency.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()
|
||||
{
|
||||
if(mTimeslotFrequency != null)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.beans.property.SimpleLongProperty;
|
||||
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 class TimeslotFrequency
|
||||
* Downlink Frequency property
|
||||
*/
|
||||
@JsonIgnore
|
||||
public LongProperty downlinkFrequencyPropertyProperty()
|
||||
public LongProperty downlinkFrequencyProperty()
|
||||
{
|
||||
return mDownlinkFrequencyProperty;
|
||||
}
|
||||
@@ -168,7 +168,7 @@ public class TimeslotFrequency
|
||||
*/
|
||||
public static Callback<TimeslotFrequency,Observable[]> 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()};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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 abstract class DMRBurst extends DMRMessage
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -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.UnconfirmedDataH
|
||||
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 class DMRDataMessageFactory
|
||||
}
|
||||
else
|
||||
{
|
||||
MotorolaProprietaryDataHeader mprdh = new MotorolaProprietaryDataHeader(pattern,
|
||||
MotorolaDataEncryptionHeader mprdh = new MotorolaDataEncryptionHeader(pattern,
|
||||
payload, cach, slotType, timestamp, timeslot);
|
||||
mprdh.setValid(valid);
|
||||
return mprdh;
|
||||
|
||||
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
||||
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 @@ import java.util.List;
|
||||
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<Identifier> mIdentifiers;
|
||||
|
||||
@@ -286,37 +285,23 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
|
||||
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 class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == getRestChannel().getLogicalSlotNumber())
|
||||
if(timeslotFrequency.getNumber() == getRestChannel().getValue())
|
||||
{
|
||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
||||
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<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
@@ -80,7 +78,7 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
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 class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
/**
|
||||
* 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 class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
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 class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
||||
if(getRestChannel().getValue() == timeslotFrequency.getNumber())
|
||||
{
|
||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.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<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
@@ -117,32 +116,23 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
|
||||
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 class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
|
||||
if(timeslotFrequency.getNumber() == getChannel().getValue())
|
||||
{
|
||||
getChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
||||
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<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
@@ -107,32 +106,23 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
||||
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 class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == getDataChannel().getLogicalSlotNumber())
|
||||
if(timeslotFrequency.getNumber() == getDataChannel().getValue())
|
||||
{
|
||||
getDataChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,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.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<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
@@ -132,32 +131,23 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
|
||||
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 class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
|
||||
if(timeslotFrequency.getNumber() == getChannel().getValue())
|
||||
{
|
||||
getChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
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 class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||
* 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 class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> 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);
|
||||
}
|
||||
|
||||
@@ -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.bits.CorrectedBinaryMessage;
|
||||
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 class AdjacentSiteInformation extends Announcement implements ITimeslotFr
|
||||
|
||||
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<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(getNeighborChannel() instanceof DMRLogicalChannel)
|
||||
{
|
||||
DMRLogicalChannel channel = (DMRLogicalChannel)getNeighborChannel();
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
||||
{
|
||||
channel.setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -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.bits.CorrectedBinaryMessage;
|
||||
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.data.mbc.MBCContinuationBloc
|
||||
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 class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
|
||||
* 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 class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> 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);
|
||||
}
|
||||
|
||||
@@ -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 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
|
||||
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.csbk.CSBKMessage;
|
||||
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 abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
|
||||
* 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 abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> 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);
|
||||
}
|
||||
|
||||
@@ -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.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.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<Identifier> mIdentifiers;
|
||||
|
||||
|
||||
@@ -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 class ProprietaryDataHeader extends DataHeader
|
||||
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.
|
||||
|
||||
@@ -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 class MNISProprietaryDataHeader extends ProprietaryDataHeader
|
||||
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());
|
||||
|
||||
@@ -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 @@ import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
||||
/**
|
||||
* 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 class MotorolaProprietaryDataHeader extends ProprietaryDataHeader
|
||||
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
|
||||
*/
|
||||
@@ -37,8 +37,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGr
|
||||
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 @@ public class LCMessageFactory
|
||||
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 @@ public class LCMessageFactory
|
||||
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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 @@ import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||
*
|
||||
* 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};
|
||||
|
||||
|
||||
@@ -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 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera;
|
||||
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};
|
||||
|
||||
@@ -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 class CapacityPlusEncryptedVoiceChannelUser extends CapacityPlusVoiceChan
|
||||
{
|
||||
return getMessage().getHex(UNKNOWN_2, 2);
|
||||
}
|
||||
public String getUnknown3()
|
||||
{
|
||||
return getMessage().getHex(UNKNOWN_3, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Source radio address
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.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<Identifier> 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<Identifier> 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<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
||||
{
|
||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ 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.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.ArrayList;
|
||||
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<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
@@ -82,18 +84,12 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
||||
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());
|
||||
sb.append(" ").append(getRestChannel());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("--");
|
||||
}
|
||||
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 class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
||||
/**
|
||||
* Unknown1 8-bit field
|
||||
*/
|
||||
public int getUnknown1()
|
||||
public String getUnknown1()
|
||||
{
|
||||
return getMessage().getInt(UNKNOWN_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown2 8-bit field
|
||||
*/
|
||||
public int getUnknown2()
|
||||
{
|
||||
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 class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
||||
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 class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
||||
{
|
||||
if(mRadio == null)
|
||||
{
|
||||
mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
|
||||
mRadio = DMRRadio.createFrom(getMessage().getInt(RADIO_ID));
|
||||
}
|
||||
|
||||
return mRadio;
|
||||
@@ -177,7 +152,7 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
||||
{
|
||||
if(mTalkgroup == null)
|
||||
{
|
||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
|
||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ID));
|
||||
}
|
||||
|
||||
return mTalkgroup;
|
||||
@@ -201,9 +176,9 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
||||
* 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 class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
||||
if(getRestChannel().getValue() == timeslotFrequency.getNumber())
|
||||
{
|
||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.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
|
||||
* <p>
|
||||
* Note: observed as FLC payload for a PI_HEADER slot type.
|
||||
* Note: observed on a possible Hytera (clone) system that was configured as IP Site Connect compatible.
|
||||
*/
|
||||
public class 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<Identifier> 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<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkgroup());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.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<Identifier> 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<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkgroup());
|
||||
mIdentifiers.add(getRadio());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -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 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
|
||||
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 class ActivityUpdateMessage extends ShortLCMessage
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
|
||||
|
||||
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 @@ import java.util.List;
|
||||
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<Identifier> mIdentifiers;
|
||||
|
||||
@@ -63,7 +61,7 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
||||
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 class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
||||
/**
|
||||
* 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 class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
||||
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 class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
||||
if(getRestChannel().getValue() == timeslotFrequency.getNumber())
|
||||
{
|
||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
|
||||
@@ -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 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
|
||||
|
||||
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 class NullMessage extends ShortLCMessage
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.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 class DMRPacketMessage extends DMRMessage
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -89,6 +90,15 @@ public class PacketSequence
|
||||
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;
|
||||
|
||||
@@ -29,7 +29,10 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataH
|
||||
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 @@ public class PacketSequenceMessageFactory
|
||||
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 class PacketSequenceMessageFactory
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public class CapacityPlusServiceOptions extends ServiceOptions
|
||||
|
||||
if(isInterruptible())
|
||||
{
|
||||
flags.add("TXI CALL");
|
||||
flags.add("INTERRUPTIBLE CALL"); //Indicates another user can interrupt this call
|
||||
}
|
||||
|
||||
if(getPriority() > 0)
|
||||
|
||||
@@ -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 @@ package io.github.dsheirer.module.decode.dmr.message.voice;
|
||||
|
||||
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,
|
||||
|
||||
@@ -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.BinaryMessage;
|
||||
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 class VoiceEMBMessage extends VoiceMessage
|
||||
|
||||
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 class VoiceEMBMessage extends VoiceMessage
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.voice;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
@@ -5,7 +24,6 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
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 @@ import java.util.List;
|
||||
*/
|
||||
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 abstract class VoiceMessage extends DMRBurst
|
||||
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.
|
||||
* <p>
|
||||
* 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()
|
||||
{
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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.
|
||||
* <p>
|
||||
* See patent: https://patents.google.com/patent/EP2347540B1/en - embedding encryption parameters in voice super frame
|
||||
* See patent: https://patents.google.com/patent/US8271009B2 - TXI - interrupting voice transmissions
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.voice.embedded;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Transmit Interrupt short burst
|
||||
* <p>
|
||||
* 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 + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user