mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Compare commits
7 Commits
v0.6.0-bet
...
v0.6.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c96349fa1c | ||
|
|
f640576cc3 | ||
|
|
77d725a488 | ||
|
|
2c0305f928 | ||
|
|
21fd61d507 | ||
|
|
8fb56a5342 | ||
|
|
5d7b73f876 |
@@ -53,7 +53,7 @@ repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.6.0-beta1'
|
||||
version = '0.6.0-beta2'
|
||||
|
||||
//Java 20 is required for this version of the Project Panama preview/incubator feature
|
||||
java {
|
||||
|
||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.buffer.INativeBufferProvider;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.log.LoggingSuppressor;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
@@ -65,6 +66,7 @@ import org.slf4j.LoggerFactory;
|
||||
public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
{
|
||||
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LoggerFactory.getLogger(PolyphaseChannelManager.class));
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||
@@ -203,7 +205,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
|
||||
LOGGING_SUPPRESSOR.error(iae.getMessage(), 3, "Couldn't allocate channel. " + iae.getMessage());
|
||||
channelSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
private ToggleSwitch mUseCompressedTalkgroupsToggle;
|
||||
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
||||
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
||||
private IntegerTextField mLogicalSlotNumberField;
|
||||
private IntegerTextField mLogicalChannelNumberField;
|
||||
private FrequencyField mDownlinkFrequencyField;
|
||||
// private FrequencyField mUplinkFrequencyField;
|
||||
private Button mAddTimeslotFrequencyButton;
|
||||
@@ -172,7 +172,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row);
|
||||
gridPane.getChildren().add(useCompressedTalkgroupsLabel);
|
||||
|
||||
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
|
||||
Label timeslotTableLabel = new Label("Logical Channel Number (LCN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies. LSN = Logical Slot Number");
|
||||
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
|
||||
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
||||
gridPane.getChildren().add(timeslotTableLabel);
|
||||
@@ -194,17 +194,13 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
editorBox.setAlignment(Pos.CENTER_LEFT);
|
||||
editorBox.setSpacing(5);
|
||||
|
||||
Label lsnLabel = new Label("LSN");
|
||||
editorBox.getChildren().addAll(lsnLabel,getLogicalSlotNumberField());
|
||||
Label lcnLabel = new Label("LCN");
|
||||
editorBox.getChildren().addAll(lcnLabel, getLogicalChannelNumberField());
|
||||
|
||||
Label downlinkLabel = new Label("Frequency (MHz)");
|
||||
downlinkLabel.setPadding(new Insets(0,0,0,5));
|
||||
editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField());
|
||||
|
||||
// Label uplinkLabel = new Label("Uplink");
|
||||
// uplinkLabel.setPadding(new Insets(0,0,0,5));
|
||||
// editorBox.getChildren().addAll(uplinkLabel,getUplinkFrequencyField());
|
||||
|
||||
GridPane.setConstraints(editorBox, 0, row, 4, 1);
|
||||
gridPane.getChildren().add(editorBox);
|
||||
|
||||
@@ -287,20 +283,22 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor()));
|
||||
mTimeslotFrequencyTable.setPrefHeight(100.0);
|
||||
|
||||
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LSN");
|
||||
numberColumn.setPrefWidth(70);
|
||||
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LCN");
|
||||
numberColumn.setPrefWidth(75);
|
||||
numberColumn.setCellValueFactory(cellData -> cellData.getValue().getNumberProperty());
|
||||
mTimeslotFrequencyTable.getColumns().addAll(numberColumn);
|
||||
mTimeslotFrequencyTable.getSortOrder().add(numberColumn);
|
||||
|
||||
TableColumn<TimeslotFrequency,Number> downlinkColumn = new TableColumn("Frequency (MHz)");
|
||||
downlinkColumn.setCellValueFactory(cellData -> cellData.getValue().getDownlinkMHz());
|
||||
downlinkColumn.setPrefWidth(175);
|
||||
downlinkColumn.setPrefWidth(150);
|
||||
mTimeslotFrequencyTable.getColumns().addAll(downlinkColumn);
|
||||
|
||||
// TableColumn<TimeslotFrequency,Number> uplinkColumn = new TableColumn("Uplink (MHz)");
|
||||
// uplinkColumn.setCellValueFactory(cellData -> cellData.getValue().getUplinkMHz());
|
||||
// mTimeslotFrequencyTable.getColumns().addAll(uplinkColumn);
|
||||
TableColumn<TimeslotFrequency,String> lsnColumn = new TableColumn("IDs (TS1/TS2)");
|
||||
lsnColumn.setPrefWidth(225);
|
||||
lsnColumn.setCellValueFactory(new PropertyValueFactory<>("logicalSlotNumbers"));
|
||||
mTimeslotFrequencyTable.getColumns().addAll(lsnColumn);
|
||||
|
||||
|
||||
mTimeslotFrequencyTable.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> setTimeslot(newValue));
|
||||
@@ -317,20 +315,20 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
//Preserve the current modified flag state since setting values in the editor will change it.
|
||||
boolean modified = modifiedProperty().get();
|
||||
|
||||
getLogicalSlotNumberField().setDisable(timeslot == null);
|
||||
getLogicalChannelNumberField().setDisable(timeslot == null);
|
||||
getDownlinkFrequencyField().setDisable(timeslot == null);
|
||||
// getUplinkFrequencyField().setDisable(timeslot == null);
|
||||
getDeleteTimeslotFrequencyButton().setDisable(timeslot == null);
|
||||
|
||||
if(timeslot != null)
|
||||
{
|
||||
getLogicalSlotNumberField().set(timeslot.getNumber());
|
||||
getLogicalChannelNumberField().set(timeslot.getNumber());
|
||||
getDownlinkFrequencyField().set(timeslot.getDownlinkFrequency());
|
||||
// getUplinkFrequencyField().set(timeslot.getUplinkFrequency());
|
||||
}
|
||||
else
|
||||
{
|
||||
getLogicalSlotNumberField().set(0);
|
||||
getLogicalChannelNumberField().set(0);
|
||||
getDownlinkFrequencyField().set(0);
|
||||
// getUplinkFrequencyField().set(0);
|
||||
}
|
||||
@@ -412,19 +410,19 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
return mDeleteTimeslotFrequencyButton;
|
||||
}
|
||||
|
||||
private IntegerTextField getLogicalSlotNumberField()
|
||||
private IntegerTextField getLogicalChannelNumberField()
|
||||
{
|
||||
if(mLogicalSlotNumberField == null)
|
||||
if(mLogicalChannelNumberField == null)
|
||||
{
|
||||
mLogicalSlotNumberField = new IntegerTextField();
|
||||
mLogicalSlotNumberField.setDisable(true);
|
||||
mLogicalSlotNumberField.setPrefWidth(65);
|
||||
mLogicalSlotNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
mLogicalChannelNumberField = new IntegerTextField();
|
||||
mLogicalChannelNumberField.setDisable(true);
|
||||
mLogicalChannelNumberField.setPrefWidth(65);
|
||||
mLogicalChannelNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
Integer value = mLogicalSlotNumberField.get();
|
||||
Integer value = mLogicalChannelNumberField.get();
|
||||
|
||||
if(value != null)
|
||||
{
|
||||
@@ -436,7 +434,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
});
|
||||
}
|
||||
|
||||
return mLogicalSlotNumberField;
|
||||
return mLogicalChannelNumberField;
|
||||
}
|
||||
|
||||
private FrequencyField getDownlinkFrequencyField()
|
||||
@@ -610,8 +608,8 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
getTimeslotTable().setDisable(config == null);
|
||||
getAddTimeslotFrequencyButton().setDisable(config == null);
|
||||
getDeleteTimeslotFrequencyButton().setDisable(true);
|
||||
getLogicalSlotNumberField().set(0);
|
||||
getLogicalSlotNumberField().setDisable(true);
|
||||
getLogicalChannelNumberField().set(0);
|
||||
getLogicalChannelNumberField().setDisable(true);
|
||||
getDownlinkFrequencyField().set(0);
|
||||
getDownlinkFrequencyField().setDisable(true);
|
||||
// getUplinkFrequencyField().set(0);
|
||||
|
||||
@@ -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
|
||||
@@ -41,13 +41,12 @@ import io.github.dsheirer.rrapi.type.Tag;
|
||||
import io.github.dsheirer.rrapi.type.Talkgroup;
|
||||
import io.github.dsheirer.rrapi.type.Type;
|
||||
import io.github.dsheirer.rrapi.type.Voice;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Utility class for decoding type, flavor and voice for systems and formatting talkgroups according to user preferences.
|
||||
@@ -374,16 +373,10 @@ public class RadioReferenceDecoder
|
||||
}
|
||||
}
|
||||
|
||||
int lsn = (lcn - 1) * 2 + 1;
|
||||
TimeslotFrequency timeslot1Frequency = new TimeslotFrequency();
|
||||
timeslot1Frequency.setNumber(lsn);
|
||||
timeslot1Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
||||
frequencies.add(timeslot1Frequency);
|
||||
|
||||
TimeslotFrequency timeslot2Frequency = new TimeslotFrequency();
|
||||
timeslot2Frequency.setNumber(lsn + 1);
|
||||
timeslot2Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
||||
frequencies.add(timeslot2Frequency);
|
||||
TimeslotFrequency timeslotFrequency = new TimeslotFrequency();
|
||||
timeslotFrequency.setNumber(lcn);
|
||||
timeslotFrequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
||||
frequencies.add(timeslotFrequency);
|
||||
}
|
||||
|
||||
return frequencies;
|
||||
|
||||
@@ -57,6 +57,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcem
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.VoteNowAdvice;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.ChannelGrant;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
||||
@@ -74,19 +75,24 @@ import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.Arc4EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.shortdata.HyteraShortDataPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.ars.ARSPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||
import io.github.dsheirer.util.PacketUtil;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -105,7 +111,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
private DecodeEvent mCurrentCallEvent;
|
||||
private long mCurrentFrequency;
|
||||
private boolean mIgnoreCRCChecksums;
|
||||
private Map<Integer,DecodeEvent> mDetectedCallEventsMap = new TreeMap<>();
|
||||
private Map<DMRChannel,DecodeEvent> mDetectedCallEventsMap = new HashMap<>();
|
||||
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
||||
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
||||
|
||||
@@ -320,6 +326,46 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
.build();
|
||||
broadcast(smsEvent);
|
||||
}
|
||||
//Hytera Radio Registration Service (RRS)
|
||||
else if(packet.getPacket() instanceof HyteraRrsPacket rrs)
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("HYTERA RRS REGISTER RADIO:");
|
||||
sb.append(rrs.getDestination());
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(sb.toString())
|
||||
.build();
|
||||
broadcast(shortDataEvent);
|
||||
}
|
||||
//Hytera Short Data
|
||||
else if(packet.getPacket() instanceof HyteraShortDataPacket hsdp)
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("HYTERA");
|
||||
|
||||
if(hsdp.getPacketSequence().isEncrypted())
|
||||
{
|
||||
HyteraDataEncryptionHeader hdeh = (HyteraDataEncryptionHeader)hsdp.getPacketSequence().getProprietaryDataHeader();
|
||||
sb.append(" ENCRYPTED ALGORITHM:").append(hdeh.getAlgorithm());
|
||||
sb.append(" KEY:").append(hdeh.getKeyId());
|
||||
sb.append(" IV:").append(hdeh.getIV());
|
||||
}
|
||||
|
||||
sb.append(" SHORT DATA:").append(hsdp.getMessage().toHexString());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(sb.toString())
|
||||
.build();
|
||||
broadcast(shortDataEvent);
|
||||
}
|
||||
//Unknown Hytera Long Data Service Token Message
|
||||
else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket)
|
||||
{
|
||||
@@ -328,14 +374,50 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
||||
.details("HYTERA LONG DATA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
||||
.build();
|
||||
broadcast(unknownTokenEvent);
|
||||
}
|
||||
//Motorola ARS
|
||||
else if(packet.getPacket() instanceof ARSPacket ars)
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(ars.toString())
|
||||
.build();
|
||||
broadcast(shortDataEvent);
|
||||
}
|
||||
//Motorola LRRP
|
||||
else if(packet.getPacket() instanceof LRRPPacket lrrp)
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.LRRP, packet.getTimestamp())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(lrrp.toString())
|
||||
.build();
|
||||
broadcast(shortDataEvent);
|
||||
}
|
||||
//Motorola XCMP
|
||||
else if(packet.getPacket() instanceof XCMPPacket xcmp)
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.XCMP, packet.getTimestamp())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(xcmp.toString())
|
||||
.build();
|
||||
broadcast(shortDataEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
|
||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
.details(packet.toString())
|
||||
.build();
|
||||
@@ -347,7 +429,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
if (geoPosition != null) {
|
||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||
.protocol(Protocol.LRRP)
|
||||
.location(geoPosition)
|
||||
.build();
|
||||
@@ -380,7 +462,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
EmbeddedParameters embedded = voiceEmb.getEmbeddedParameters();
|
||||
|
||||
if(embedded.getShortBurst() instanceof Arc4EncryptionParameters arc4)
|
||||
if(embedded.getShortBurst() instanceof EncryptionParameters arc4)
|
||||
{
|
||||
updateEncryptedCall(arc4, true, voiceEmb.getTimestamp());
|
||||
}
|
||||
@@ -653,21 +735,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
ChannelGrant dataGrant = (ChannelGrant)csbk;
|
||||
DMRChannel channel = dataGrant.getChannel();
|
||||
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getValue(), event);
|
||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -686,21 +769,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
ChannelGrant tgGrant = (ChannelGrant)csbk;
|
||||
DMRChannel channel = tgGrant.getChannel();
|
||||
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getValue(), event);
|
||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -718,21 +802,23 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
ChannelGrant channelGrant = (ChannelGrant)csbk;
|
||||
DMRChannel channel = channelGrant.getChannel();
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getValue(), event);
|
||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -763,21 +849,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
|
||||
DMRChannel channel = cpdcg.getChannel();
|
||||
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getValue());
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getValue(), event);
|
||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -793,7 +880,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
|
||||
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
|
||||
.details("Registration Request")
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
.build();
|
||||
broadcast(event);
|
||||
@@ -802,7 +889,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
||||
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
|
||||
.details("Registration Response")
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
.build();
|
||||
broadcast(regRespEvent);
|
||||
@@ -814,21 +901,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
|
||||
DMRChannel channel = cpvcu.getChannel();
|
||||
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getValue());
|
||||
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getValue(), detectedEvent);
|
||||
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, detectedEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -844,7 +932,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
|
||||
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
|
||||
.details("TALKGROUP AFFILIATION")
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
.build();
|
||||
broadcast(affiliateEvent);
|
||||
@@ -857,18 +945,18 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
|
||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel,
|
||||
IdentifierCollection mergedIdentifiers) {
|
||||
IdentifierCollection identifierCollection) {
|
||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.identifiers(identifierCollection)
|
||||
.timeslot(getTimeslot())
|
||||
.build();
|
||||
}
|
||||
|
||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) {
|
||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
.details(details)
|
||||
.build();
|
||||
@@ -972,6 +1060,15 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
switch(message.getOpcode())
|
||||
{
|
||||
case FULL_ENCRYPTION_PARAMETERS:
|
||||
if(message instanceof io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters ep)
|
||||
{
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
mCurrentCallEvent.setDetails(ep.getDetails());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION:
|
||||
if(message instanceof CapacityPlusRestChannel)
|
||||
{
|
||||
@@ -1141,7 +1238,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
* @param encryptionParameters decoded from the Voice Frame F
|
||||
* @param isGroup true for group or false for individual call.
|
||||
*/
|
||||
private void updateEncryptedCall(Arc4EncryptionParameters encryptionParameters, boolean isGroup, long timestamp)
|
||||
private void updateEncryptedCall(EncryptionParameters encryptionParameters, boolean isGroup, long timestamp)
|
||||
{
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
@@ -1151,7 +1248,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
details = encryptionParameters.toString();
|
||||
}
|
||||
else if(!details.contains(encryptionParameters.toString()))
|
||||
else if(!details.contains(encryptionParameters.toString()) && !details.contains("ENCRYPTION"))
|
||||
{
|
||||
details += " " + encryptionParameters;
|
||||
}
|
||||
|
||||
@@ -127,19 +127,19 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
}
|
||||
}
|
||||
|
||||
//Enrich messages that carry DMR Logical Slot Number channels with LCN to frequency mappings
|
||||
//Enrich messages that carry DMR Logical Channel Numbers with LCN to frequency mappings
|
||||
if(message instanceof ITimeslotFrequencyReceiver)
|
||||
{
|
||||
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
|
||||
int[] lsns = receiver.getLogicalSlotNumbers();
|
||||
int[] lcns = receiver.getLogicalChannelNumbers();
|
||||
|
||||
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
||||
|
||||
for(int lsn: lsns)
|
||||
for(int lcn: lcns)
|
||||
{
|
||||
if(mTimeslotFrequencyMap.containsKey(lsn))
|
||||
if(mTimeslotFrequencyMap.containsKey(lcn))
|
||||
{
|
||||
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lsn));
|
||||
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lcn));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 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;
|
||||
@@ -41,14 +38,13 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusCo
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusTrafficChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Model;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Tracks the network configuration details of a DMR network from the broadcast messages
|
||||
@@ -250,7 +246,7 @@ public class DMRNetworkConfigurationMonitor
|
||||
ConnectPlusNeighborReport cpnr = (ConnectPlusNeighborReport)csbk;
|
||||
mNeighborSites.addAll(cpnr.getNeighbors());
|
||||
}
|
||||
if(mBrand == null)
|
||||
if(mBrand == null || mBrand != BRAND_MOTOROLA_CONNECT_PLUS)
|
||||
{
|
||||
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
private List<Channel> mManagedTrafficChannels;
|
||||
|
||||
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
|
||||
private Map<Integer,DMRChannelGrantEvent> mLSNGrantEventMap = new ConcurrentHashMap<>();
|
||||
private Map<DMRChannel,DMRChannelGrantEvent> mChannelGrantEventMap = new ConcurrentHashMap<>();
|
||||
|
||||
private Listener<ChannelEvent> mChannelEventListener;
|
||||
private Listener<IDecodeEvent> mDecodeEventListener;
|
||||
@@ -322,9 +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.getValue();
|
||||
|
||||
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
|
||||
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||
|
||||
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
||||
@@ -335,7 +333,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
|
||||
mLSNGrantEventMap.put(lsn, event);
|
||||
mChannelGrantEventMap.put(channel, event);
|
||||
|
||||
}
|
||||
else //Update current event
|
||||
@@ -355,7 +353,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
|
||||
mLSNGrantEventMap.put(lsn, event);
|
||||
mChannelGrantEventMap.put(channel, event);
|
||||
broadcast(event);
|
||||
}
|
||||
}
|
||||
@@ -643,37 +641,37 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
*/
|
||||
private void removeCallEvents(long frequency)
|
||||
{
|
||||
List<Integer> lsnsToRemove = new ArrayList<>();
|
||||
List<DMRChannel> channelsToRemove = new ArrayList<>();
|
||||
|
||||
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
|
||||
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
||||
{
|
||||
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
||||
if(entry.getKey().getDownlinkFrequency() == frequency)
|
||||
{
|
||||
lsnsToRemove.add(entry.getKey());
|
||||
channelsToRemove.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
for(Integer lsn: lsnsToRemove)
|
||||
for(DMRChannel channel: channelsToRemove)
|
||||
{
|
||||
mLSNGrantEventMap.remove(lsn);
|
||||
mChannelGrantEventMap.remove(channel);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCallEventDetails(long frequency, String detailFragment)
|
||||
{
|
||||
List<Integer> lsnsToUpdate = new ArrayList<>();
|
||||
List<DMRChannel> channelsToUpdate = new ArrayList<>();
|
||||
|
||||
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
|
||||
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
||||
{
|
||||
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
||||
{
|
||||
lsnsToUpdate.add(entry.getKey());
|
||||
channelsToUpdate.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
for(Integer lsn: lsnsToUpdate)
|
||||
for(DMRChannel channel: channelsToUpdate)
|
||||
{
|
||||
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
|
||||
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||
|
||||
if(event != null)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,8 @@ import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
/**
|
||||
@@ -33,8 +35,9 @@ import org.apache.commons.lang3.Validate;
|
||||
*
|
||||
* Note: timeslots are tracked as 1 and 2
|
||||
*/
|
||||
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor
|
||||
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor, ITimeslotFrequencyReceiver
|
||||
{
|
||||
private TimeslotFrequency mTimeslotFrequency;
|
||||
private int mTimeslot;
|
||||
|
||||
/**
|
||||
@@ -56,13 +59,23 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeater number or channel number for the channel.
|
||||
* Repeater number aka Logical Channel Number (LCN) for the channel.
|
||||
*/
|
||||
public int getChannel()
|
||||
public int getChannelNumber()
|
||||
{
|
||||
return getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to format the single channel number as an array for TimeslotFrequency matching.
|
||||
* @return channel number as an integer array.
|
||||
*/
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return new int[]{getChannelNumber()};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Timeslot for the channel.
|
||||
* @return timeslot as zero-based index with values in range: 0 or 1.
|
||||
@@ -95,7 +108,7 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "CHAN:" + getChannel() + ":" + getTimeslot();
|
||||
return "CHAN:" + getChannelNumber() + ":" + getTimeslot();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,4 +128,72 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
||||
{
|
||||
throw new IllegalArgumentException("This method is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDownlinkFrequency()
|
||||
{
|
||||
if(mTimeslotFrequency != null)
|
||||
{
|
||||
return mTimeslotFrequency.getDownlinkFrequency();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mTimeslotFrequency != null)
|
||||
{
|
||||
return mTimeslotFrequency.getUplinkFrequency();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lsn to frequency mapper value.
|
||||
* @param timeslotFrequency to set
|
||||
*/
|
||||
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
|
||||
{
|
||||
mTimeslotFrequency = timeslotFrequency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(timeslotFrequency.getChannelNumber() == getChannelNumber())
|
||||
{
|
||||
setTimeslotFrequency(timeslotFrequency);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(!(o instanceof DMRChannel that))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(!super.equals(o))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return getTimeslot() == that.getTimeslot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(super.hashCode(), getTimeslot());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.channel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DMR Logical Slot Number (LSN) channel.
|
||||
*
|
||||
@@ -44,10 +42,8 @@ import java.util.List;
|
||||
* 15: 8/1
|
||||
* 16: 8/2
|
||||
*/
|
||||
public class DMRLsn extends DMRChannel implements ITimeslotFrequencyReceiver
|
||||
public class DMRLsn extends DMRChannel
|
||||
{
|
||||
private TimeslotFrequency mTimeslotFrequency;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
@@ -61,7 +57,7 @@ public class DMRLsn extends DMRChannel implements ITimeslotFrequencyReceiver
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "LSN:" + getLsn();
|
||||
return "LSN:" + getLsn() + " LCN:" + getChannelNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,65 +66,7 @@ public class DMRLsn extends DMRChannel implements ITimeslotFrequencyReceiver
|
||||
*/
|
||||
public int getLsn()
|
||||
{
|
||||
return ((getChannel() - 1) * 2) + getTimeslot();
|
||||
return ((getChannelNumber() - 1) * 2) + getTimeslot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDownlinkFrequency()
|
||||
{
|
||||
if(mTimeslotFrequency != null)
|
||||
{
|
||||
return mTimeslotFrequency.getDownlinkFrequency();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUplinkFrequency()
|
||||
{
|
||||
if(mTimeslotFrequency != null)
|
||||
{
|
||||
return mTimeslotFrequency.getUplinkFrequency();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
{
|
||||
return new int[]{getLsn()};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the lsn to frequency mapper value.
|
||||
* @param timeslotFrequency to set
|
||||
*/
|
||||
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
|
||||
{
|
||||
mTimeslotFrequency = timeslotFrequency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,12 @@ package io.github.dsheirer.module.decode.dmr.channel;
|
||||
*/
|
||||
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 logicalSlotNumber - zero based index.
|
||||
* @param timeslot either 1 or 2.
|
||||
*/
|
||||
public DMRTier3Channel(int channel, int timeslot)
|
||||
{
|
||||
@@ -39,42 +37,12 @@ public class DMRTier3Channel extends DMRChannel
|
||||
}
|
||||
|
||||
/**
|
||||
* Downlink frequency
|
||||
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
|
||||
* Tier III Channel ID
|
||||
* @return channel ID
|
||||
*/
|
||||
@Override
|
||||
public long getDownlinkFrequency()
|
||||
public int getChannelId()
|
||||
{
|
||||
if(mTimeslotFrequency != null)
|
||||
{
|
||||
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;
|
||||
return getChannelNumber() * 2 + getTimeslot();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +51,8 @@ public class DMRTier3Channel extends DMRChannel
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(" LCN:").append(getValue());
|
||||
sb.append(" LCN:").append(getChannelNumber());
|
||||
sb.append(" CHANID:").append(getChannelId());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import java.util.List;
|
||||
public interface ITimeslotFrequencyReceiver
|
||||
{
|
||||
/**
|
||||
* Provides the logical slot number(s) that require a matching timeslot frequency mapping
|
||||
* Provides the logical channel number(s) that require a matching timeslot frequency mapping
|
||||
*/
|
||||
public int[] getLogicalSlotNumbers();
|
||||
public int[] getLogicalChannelNumbers();
|
||||
|
||||
/**
|
||||
* Applies the list of timeslot frequency mappings to the implementer
|
||||
|
||||
@@ -31,7 +31,7 @@ import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.util.Callback;
|
||||
|
||||
/**
|
||||
* Maps a logical slot number (LSN) to a pair of channel frequency values
|
||||
* Maps a logical channel number (LCN) to a pair of channel frequency values
|
||||
*/
|
||||
public class TimeslotFrequency
|
||||
{
|
||||
@@ -100,7 +100,10 @@ public class TimeslotFrequency
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical slot number (LSN) as a 1-index based counter
|
||||
* Logical channel number (LCN) as a 1-index based counter
|
||||
*
|
||||
* Note: lsn (logical slot number) is a legacy name for this field. The field currently holds the LCN or logical
|
||||
* channel number. To avoid breaking serialization, we leave this field labeled as lsn.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "lsn")
|
||||
public int getNumber()
|
||||
@@ -109,12 +112,52 @@ public class TimeslotFrequency
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the logical slot number (LSN) as a 1-index start
|
||||
* @param lsn where LSN 1 is the first slot, 2 the second, etc
|
||||
* Logical channel number which is the same value as getNumber.
|
||||
* @return
|
||||
*/
|
||||
public void setNumber(int lsn)
|
||||
@JsonIgnore
|
||||
public int getChannelNumber()
|
||||
{
|
||||
mNumberProperty.set(lsn);
|
||||
return getNumber();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getLogicalSlotNumbers()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int channel = getChannelNumber();
|
||||
|
||||
if(channel > 0)
|
||||
{
|
||||
if(channel < 32) //Connect Plus uses 5-bits for LSN for a maximum value of 31
|
||||
{
|
||||
//Cap+ & Con+ Logical Slot Numbers
|
||||
int lsn1 = (channel - 1) * 2 + 1;
|
||||
int lsn2 = (channel - 1) * 2 + 2;
|
||||
sb.append("LSN:").append(lsn1).append("/").append(lsn2).append(" or ");
|
||||
}
|
||||
|
||||
//Tier III channel IDs
|
||||
int chanId1 = channel * 2 + 1;
|
||||
int chanId2 = channel * 2 + 2;
|
||||
sb.append("Tier3 Chan:").append(chanId1).append("/").append(chanId2);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("(empty)");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the logical channel number (LCN) as a 1-index start
|
||||
* @param lcn where LCN 1 is logical slot numbers (LSN) 1 and 2, LCN2=LSN3/4, etc.
|
||||
*/
|
||||
public void setNumber(int lcn)
|
||||
{
|
||||
mNumberProperty.set(lcn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +203,7 @@ public class TimeslotFrequency
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "TIMESLOT LSN:" + getNumber() + " DOWNLINK:" + getDownlinkFrequency();
|
||||
return "LCN:" + getNumber() + " DOWNLINK:" + getDownlinkFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,8 +33,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ConfirmedDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.DataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.DefinedShortDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PiHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.RawShortDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ResponseDataHeader;
|
||||
@@ -43,7 +43,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.StatusDataHeader
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UnconfirmedDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||
@@ -98,7 +98,7 @@ public class DMRDataMessageFactory
|
||||
return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||
case CHANNEL_CONTROL_ENC_HEADER:
|
||||
case PI_HEADER:
|
||||
return new HeaderMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||
return new PiHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||
case VOICE_HEADER:
|
||||
return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||
case DATA_ENC_HEADER:
|
||||
@@ -138,10 +138,10 @@ public class DMRDataMessageFactory
|
||||
return mprdh;
|
||||
}
|
||||
case HYTERA_68:
|
||||
HyteraProprietaryDataHeader hpdh = new HyteraProprietaryDataHeader(pattern, payload,
|
||||
cach, slotType, timestamp, timeslot);
|
||||
hpdh.setValid(valid);
|
||||
return hpdh;
|
||||
HyteraDataEncryptionHeader hsdh = new HyteraDataEncryptionHeader(pattern, payload,
|
||||
cach, slotType, timestamp, timeslot);
|
||||
hsdh.setValid(valid);
|
||||
return hsdh;
|
||||
default:
|
||||
ProprietaryDataHeader pdh = new ProprietaryDataHeader(pattern, payload, cach,
|
||||
slotType, timestamp, timeslot);
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.util.List;
|
||||
*/
|
||||
public abstract class DataMessageWithLinkControl extends DataMessage
|
||||
{
|
||||
private LCMessage mLCMessage;
|
||||
protected LCMessage mLCMessage;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
|
||||
@@ -56,6 +56,10 @@ public class DataBlock1Rate extends DataBlock
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||
}
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
|
||||
@@ -56,6 +56,10 @@ public class DataBlock1_2Rate extends DataBlock
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||
}
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
|
||||
@@ -57,6 +57,10 @@ public class DataBlock3_4Rate extends DataBlock
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||
}
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Control Signalling Block (CSBK) Opcode enumeration
|
||||
*
|
||||
* ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B
|
||||
* ETSI TS 102 361-4 DMR Trunking Protocol, Annex B
|
||||
*/
|
||||
@@ -91,6 +90,8 @@ public enum Opcode
|
||||
|
||||
HYTERA_08_ACKNOWLEDGE(Vendor.HYTERA_8, 32, "HYTERA 08 ACKNOWLEDGE"),
|
||||
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "HYTERA 08 ANNOUNCEMENT"),
|
||||
//CSBKO 44 & 47 observed on Tier3 interleaved in voice group call terminator sequence. 44 was continuously transmitted
|
||||
//and 47 was only transmitted 3x times in succession in the middle of the terminators and CSBKO 44 messages.
|
||||
HYTERA_08_CSBKO_44(Vendor.HYTERA_8, 44, "HYTERA 08 CSBKO 44"),
|
||||
HYTERA_08_CSBKO_47(Vendor.HYTERA_8, 47, "HYTERA 08 CSBKO 47"),
|
||||
|
||||
@@ -104,9 +105,9 @@ public enum Opcode
|
||||
|
||||
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
|
||||
|
||||
private Vendor mVendor;
|
||||
private int mValue;
|
||||
private String mLabel;
|
||||
private final Vendor mVendor;
|
||||
private final int mValue;
|
||||
private final String mLabel;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -299,9 +299,9 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return getRestChannel().getLogicalSlotNumbers();
|
||||
return getRestChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,13 +312,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == getRestChannel().getValue())
|
||||
{
|
||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getRestChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -299,9 +299,9 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
* Logical slot numbers that require slot to frequency mappings.
|
||||
*/
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return getRestChannel().getLogicalSlotNumbers();
|
||||
return getRestChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,13 +312,7 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
if(getRestChannel().getValue() == timeslotFrequency.getNumber())
|
||||
{
|
||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getRestChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -40,7 +40,7 @@ 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, 44};
|
||||
private static final int[] LOGICAL_SLOT_NUMBER = 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};
|
||||
@@ -109,11 +109,11 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel grant repeater number
|
||||
* Channel grant logical slot number
|
||||
*/
|
||||
public int getRepeater()
|
||||
public int getLsn()
|
||||
{
|
||||
return getMessage().getInt(REPEATER);
|
||||
return getMessage().getInt(LOGICAL_SLOT_NUMBER) - 1; //Don't change this ... always subtract 1 to get the repeater LSN
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,16 +123,16 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
|
||||
{
|
||||
if(mDmrLsn == null)
|
||||
{
|
||||
mDmrLsn = new DMRLsn(getRepeater());
|
||||
mDmrLsn = new DMRLsn(getLsn());
|
||||
}
|
||||
|
||||
return mDmrLsn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return getChannel().getLogicalSlotNumbers();
|
||||
return getChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,13 +142,7 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == getChannel().getValue())
|
||||
{
|
||||
getChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -45,7 +45,7 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
||||
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};
|
||||
private static final int[] DATA_REPEATER = new int[]{63, 64, 65, 66, 67};
|
||||
private static final int[] LOGICAL_SLOT_NUMBER = new int[]{63, 64, 65, 66, 67};
|
||||
|
||||
private DMRLsn mDataChannel;
|
||||
private List<Identifier> mIdentifiers;
|
||||
@@ -99,11 +99,11 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
||||
}
|
||||
|
||||
/**
|
||||
* Data repeater number
|
||||
* Data logical slot numberrepeater number
|
||||
*/
|
||||
public int getDataRepeater()
|
||||
public int getDataLsn()
|
||||
{
|
||||
return getMessage().getInt(DATA_REPEATER);
|
||||
return getMessage().getInt(LOGICAL_SLOT_NUMBER) - 1; //Leave this as minus 1
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,16 +113,16 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
||||
{
|
||||
if(mDataChannel == null)
|
||||
{
|
||||
mDataChannel = new DMRLsn(getDataRepeater());
|
||||
mDataChannel = new DMRLsn(getDataLsn());
|
||||
}
|
||||
|
||||
return mDataChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return getDataChannel().getLogicalSlotNumbers();
|
||||
return getDataChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,13 +133,7 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == getDataChannel().getValue())
|
||||
{
|
||||
getDataChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getDataChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -44,7 +44,7 @@ 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, 68};
|
||||
private static final int[] LOGICAL_SLOT_NUMBER = 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;
|
||||
@@ -126,9 +126,9 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
|
||||
/**
|
||||
* Traffic channel repeater
|
||||
*/
|
||||
public int getTrafficChannelRepeater()
|
||||
public int getTrafficLsn()
|
||||
{
|
||||
return getMessage().getInt(TRAFFIC_CHANNEL_REPEATER);
|
||||
return getMessage().getInt(LOGICAL_SLOT_NUMBER) - 1; //Always leave as minus 1
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,16 +138,16 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
|
||||
{
|
||||
if(mDmrLsn == null)
|
||||
{
|
||||
mDmrLsn = new DMRLsn(getTrafficChannelRepeater());
|
||||
mDmrLsn = new DMRLsn(getTrafficLsn());
|
||||
}
|
||||
|
||||
return mDmrLsn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return getChannel().getLogicalSlotNumbers();
|
||||
return getChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,13 +157,7 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == getChannel().getValue())
|
||||
{
|
||||
getChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,7 +25,6 @@ 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.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 +223,11 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||
* Logical Slot Number(s) for channels contained in this message
|
||||
*/
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
if(getMoveToChannel() instanceof DMRLsn dmrLsn)
|
||||
if(getMoveToChannel() != null)
|
||||
{
|
||||
return dmrLsn.getLogicalSlotNumbers();
|
||||
return getMoveToChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
return new int[0];
|
||||
@@ -241,17 +240,9 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(getMoveToChannel() instanceof DMRTier3Channel)
|
||||
if(getMoveToChannel() != null)
|
||||
{
|
||||
DMRTier3Channel channel = (DMRTier3Channel)getMoveToChannel();
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(channel.getValue() == timeslotFrequency.getNumber())
|
||||
{
|
||||
channel.setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getMoveToChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,8 @@ public class MoveTSCC extends CSBKMessage
|
||||
|
||||
if(multiBlock != null)
|
||||
{
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
||||
//Timeslot hard-coded to 1 since this is a control channel.
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ public class AdjacentSiteInformation extends Announcement
|
||||
|
||||
if(multiBlock != null)
|
||||
{
|
||||
//Timeslot hard-coded to zero for control channel
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
||||
//Timeslot hard-coded to one for control channel
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +27,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;
|
||||
|
||||
@@ -56,7 +55,8 @@ public class AnnounceChannelFrequency extends Announcement
|
||||
|
||||
if(multiBlock != null)
|
||||
{
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
||||
//Timeslot hard-coded to one
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +86,8 @@ public class AnnounceWithdrawTSCC extends Announcement
|
||||
|
||||
if(multiBlock != null)
|
||||
{
|
||||
//Control channel timeslot hard-coded to 0
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
||||
//Control channel timeslot hard-coded to 1
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ 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.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;
|
||||
@@ -90,7 +89,8 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
|
||||
|
||||
if(multiBlock != null)
|
||||
{
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
||||
//Timeslot hard-coded to one for control channel
|
||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,11 +239,11 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
|
||||
* Logical Slot Number(s) for channels contained in this message
|
||||
*/
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
if(getChannel() instanceof DMRLsn dmrLsn)
|
||||
if(getChannel() != null)
|
||||
{
|
||||
return dmrLsn.getLogicalSlotNumbers();
|
||||
return getChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
return new int[0];
|
||||
@@ -256,17 +256,9 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(getChannel() instanceof DMRTier3Channel)
|
||||
if(getChannel() != null)
|
||||
{
|
||||
DMRTier3Channel channel = (DMRTier3Channel)getChannel();
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(channel.getValue() == timeslotFrequency.getNumber())
|
||||
{
|
||||
channel.setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public class BroadcastTalkgroupVoiceChannelGrant extends ChannelGrant
|
||||
|
||||
sb.append(" BROADCAST TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationTalkgroup());
|
||||
sb.append(" CHAN:").append(getChannel());
|
||||
sb.append(" ").append(getChannel());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ 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.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;
|
||||
@@ -158,11 +157,11 @@ public abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
|
||||
* Logical Slot Number(s) for channels contained in this message
|
||||
*/
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
if(getChannel() instanceof DMRLsn dmrLsn)
|
||||
if(getChannel() != null)
|
||||
{
|
||||
return dmrLsn.getLogicalSlotNumbers();
|
||||
return getChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
return new int[0];
|
||||
@@ -175,17 +174,9 @@ public abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(getChannel() instanceof DMRTier3Channel)
|
||||
if(getChannel() != null)
|
||||
{
|
||||
DMRTier3Channel channel = (DMRTier3Channel)getChannel();
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(channel.getValue() == timeslotFrequency.getNumber())
|
||||
{
|
||||
channel.setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public class DuplexPrivateDataChannelGrant extends ChannelGrant
|
||||
|
||||
sb.append(" DUPLEX PRIVATE DATA CHANNEL GRANT FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationRadio());
|
||||
sb.append(" CHAN:").append(getChannel());
|
||||
sb.append(" ").append(getChannel());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ public class DuplexPrivateVoiceChannelGrant extends ChannelGrant
|
||||
|
||||
sb.append(" DUPLEX PRIVATE VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationRadio());
|
||||
sb.append(" CHAN:").append(getChannel());
|
||||
sb.append(" ").append(getChannel());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ public class PrivateDataChannelGrant extends ChannelGrant
|
||||
|
||||
sb.append(" PRIVATE DATA CHANNEL GRANT FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationRadio());
|
||||
sb.append(" CHAN:").append(getChannel());
|
||||
sb.append(" ").append(getChannel());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ public class PrivateVoiceChannelGrant extends ChannelGrant
|
||||
|
||||
sb.append(" PRIVATE VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationRadio());
|
||||
sb.append(" CHAN:").append(getChannel());
|
||||
sb.append(" ").append(getChannel());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ public class TalkgroupDataChannelGrant extends ChannelGrant
|
||||
|
||||
sb.append(" TALKGROUP DATA CHANNEL GRANT FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationTalkgroup());
|
||||
sb.append(" CHAN:").append(getChannel());
|
||||
sb.append(" ").append(getChannel());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ public class TalkgroupVoiceChannelGrant extends ChannelGrant
|
||||
|
||||
sb.append(" TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationTalkgroup());
|
||||
sb.append(" CHAN:").append(getChannel());
|
||||
sb.append(" ").append(getChannel());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,18 +17,19 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.header.hytera;
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.header;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
|
||||
|
||||
/**
|
||||
* Hytera Proprietary Data Header
|
||||
* PI/Encryption Header with Link Control
|
||||
*/
|
||||
public class HyteraProprietaryDataHeader extends ProprietaryDataHeader
|
||||
public class PiHeader extends HeaderMessage
|
||||
{
|
||||
/**
|
||||
* Constructs an instance.
|
||||
@@ -40,23 +41,42 @@ public class HyteraProprietaryDataHeader extends ProprietaryDataHeader
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
*/
|
||||
public HyteraProprietaryDataHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
public PiHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the embedded link control message
|
||||
*/
|
||||
public LCMessage getLCMessage()
|
||||
{
|
||||
if(mLCMessage == null)
|
||||
{
|
||||
//Overrides default to ignore opcode and construct an encryption parameters message.
|
||||
mLCMessage = LCMessageFactory.createFullEncryption(getMessage(), getTimestamp(), getTimeslot());
|
||||
}
|
||||
|
||||
return mLCMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
}
|
||||
sb.append(" HYTERA PROPRIETARY DATA HEADER");
|
||||
sb.append(" SAP:").append(getServiceAccessPoint());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
sb.append(getSlotType());
|
||||
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append(" RAS:").append(getBPTCReservedBits()).append(" ");
|
||||
}
|
||||
sb.append(" ").append(getLCMessage());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.header.hytera;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* Hytera Proprietary Data Header for SAP Short Data
|
||||
*/
|
||||
public class HyteraDataEncryptionHeader extends ProprietaryDataHeader
|
||||
{
|
||||
private static final int[] ALGORITHM = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||
private static final int[] KEY = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static final int[] INITIALIZATION_VECTOR = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
||||
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63};
|
||||
private static final int[] UNKNOWN = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
|
||||
* @param message containing extracted 196-bit payload.
|
||||
* @param cach for the DMR burst
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
*/
|
||||
public HyteraDataEncryptionHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
}
|
||||
sb.append(" HYTERA DATA ENCRYPTION HEADER - SAP:").append(getServiceAccessPoint());
|
||||
|
||||
switch(getAlgorithm())
|
||||
{
|
||||
case NO_ENCRYPTION:
|
||||
sb.append(" ALGORITHM:UNENCRYPTED");
|
||||
break;
|
||||
case UNKNOWN:
|
||||
sb.append(" ALGORITHM:").append(getMessage().getInt(ALGORITHM));
|
||||
break;
|
||||
default:
|
||||
sb.append(" ALGORITHM:").append(getAlgorithm());
|
||||
break;
|
||||
}
|
||||
|
||||
if(getAlgorithm() != EncryptionAlgorithm.NO_ENCRYPTION)
|
||||
{
|
||||
sb.append(" KEY:").append(getKeyId());
|
||||
sb.append(" IV:").append(getIV());
|
||||
sb.append(" UNK:").append(getUnknown());
|
||||
}
|
||||
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption algorithm.
|
||||
* @return algorithm or UNKNOWN if the value is not known.
|
||||
*/
|
||||
public EncryptionAlgorithm getAlgorithm()
|
||||
{
|
||||
return EncryptionAlgorithm.fromValue(getMessage().getInt(ALGORITHM));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption key ID (1-255).
|
||||
* @return key ID
|
||||
*/
|
||||
public int getKeyId()
|
||||
{
|
||||
return getMessage().getInt(KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hex string representation of the initialization vector
|
||||
* @return IV in hex string.
|
||||
*/
|
||||
public String getIV()
|
||||
{
|
||||
return getMessage().getHex(INITIALIZATION_VECTOR, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown field.
|
||||
* @return hex string from the field contents.
|
||||
*/
|
||||
public String getUnknown()
|
||||
{
|
||||
return getMessage().getHex(UNKNOWN, 4);
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,8 @@ public class MotorolaDataEncryptionHeader extends ProprietaryDataHeader
|
||||
sb.append(" MOTOROLA DATA ENCRYPTION HEADER");
|
||||
sb.append(" SAP:").append(getServiceAccessPoint());
|
||||
sb.append(" ALGORITHM?:").append(getAlgorithm());
|
||||
sb.append(" KEY?:").append(getKeyId());
|
||||
sb.append(" IV?:").append(getInitializationVector());
|
||||
sb.append(" KEY:").append(getKeyId());
|
||||
sb.append(" IV:").append(getInitializationVector());
|
||||
sb.append(" UNK:").append(getUnknown());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
|
||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.edac.Checksum_5_DMR;
|
||||
import io.github.dsheirer.edac.ReedSolomon_12_9_4_DMR;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
||||
@@ -33,13 +34,11 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasHead
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TerminatorData;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraArc4EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaArc4EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ActivityUpdateMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
||||
@@ -61,8 +60,20 @@ public class LCMessageFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LCMessageFactory.class);
|
||||
private static final ReedSolomon_12_9_4_DMR REED_SOLOMON_12_9_4_DMR = new ReedSolomon_12_9_4_DMR();
|
||||
private static int TERMINATOR_LINK_CONTROL_CRC_MASK = 0x99;
|
||||
private static int VOICE_LINK_CONTROL_CRC_MASK = 0x96;
|
||||
private static final int TERMINATOR_LINK_CONTROL_CRC_MASK = 0x99;
|
||||
private static final int VOICE_LINK_CONTROL_CRC_MASK = 0x96;
|
||||
|
||||
/**
|
||||
* Creates a full link control message specifically for the PI_HEADER message type.
|
||||
* @param message bits
|
||||
* @param timestamp of the original message
|
||||
* @param timeslot of the original message
|
||||
* @return encryption parameters link control.
|
||||
*/
|
||||
public static FullLCMessage createFullEncryption(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
return new EncryptionParameters(message, timestamp, timeslot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a full link control message
|
||||
@@ -123,34 +134,9 @@ public class LCMessageFactory
|
||||
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
|
||||
flc = new CapacityPlusWideAreaVoiceChannelUser(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_ARC4_ENCRYPTION_PARAMETERS:
|
||||
boolean isHytera = false;
|
||||
|
||||
//This is apparently now a DMR standard FLC opcode, even though it's using Motorola's vendor ID (0x10).
|
||||
//As observed on a known Hytera system, it's not using the standard FLC RS-12/9/4 check, which fails.
|
||||
//It's apparently using the CRC-CCITT with 0x9696 initial fill. Not sure if both Hytera and Motorola
|
||||
// are using this FEC. But, by using only 16 bits for checksum versus the standad 24, they're able to
|
||||
// include a full 24-bit group/radio ID in the message. For now we'll identify as either Motorola
|
||||
//(using reed solomon) or Hytera (using crc-ccitt).
|
||||
if(!valid)
|
||||
{
|
||||
int bitErrors = CRCDMR.correctCCITT80(message, 0, 80, 0x9696);
|
||||
|
||||
if(bitErrors < 2)
|
||||
{
|
||||
valid = true;
|
||||
isHytera = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(isHytera)
|
||||
{
|
||||
flc = new HyteraArc4EncryptionParameters(message, timestamp, timeslot);
|
||||
}
|
||||
else
|
||||
{
|
||||
flc = new MotorolaArc4EncryptionParameters(message, timestamp, timeslot);
|
||||
}
|
||||
case FULL_ENCRYPTION_PARAMETERS:
|
||||
flc = new EncryptionParameters(message, timestamp, timeslot);
|
||||
valid = flc.isValid();
|
||||
break;
|
||||
case FULL_HYTERA_GROUP_VOICE_CHANNEL_USER:
|
||||
flc = new HyteraGroupVoiceChannelUser(message, timestamp, timeslot);
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Full and Short Link Control Opcode enumeration
|
||||
*
|
||||
* ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B.1 & B.2
|
||||
* ETSI TS 102 361-4 DMR Trunking Protocol, Annex B.1 & B.2
|
||||
*/
|
||||
@@ -49,7 +48,7 @@ public enum LCOpcode
|
||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||
FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),
|
||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||
FULL_ARC4_ENCRYPTION_PARAMETERS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 33, "ARC4/EP ENCRYPTION PARAMETERS"),
|
||||
FULL_ENCRYPTION_PARAMETERS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 33, "ENCRYPTION PARAMETERS"),
|
||||
//Cap+ opcodes from https://forums.radioreference.com/threads/understanding-capacity-plus-trunking-some-more.452566/
|
||||
//FLCO 0: Group Call Maintenance
|
||||
//FLCO 3: Private Call Maintenance (TermLC)
|
||||
@@ -81,10 +80,10 @@ public enum LCOpcode
|
||||
private static final EnumSet<LCOpcode> TALKER_ALIAS_OPCODES = EnumSet.of(FULL_STANDARD_TALKER_ALIAS_HEADER,
|
||||
FULL_STANDARD_TALKER_ALIAS_BLOCK_1, FULL_STANDARD_TALKER_ALIAS_BLOCK_2, FULL_STANDARD_TALKER_ALIAS_BLOCK_3);
|
||||
|
||||
private Vendor mVendor;
|
||||
private boolean mFull;
|
||||
private int mValue;
|
||||
private String mLabel;
|
||||
private final Vendor mVendor;
|
||||
private final boolean mFull;
|
||||
private final int mValue;
|
||||
private final String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
|
||||
@@ -17,29 +17,30 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera;
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hytera ARC4/EP Encryption Parameters
|
||||
* <p>
|
||||
* Note: observed as FLC payload for a PI_HEADER slot type.
|
||||
* Note: observed on a Hytera system that was configured as IP Site Connect compatible.
|
||||
* Encryption Parameters, carried by a PI_HEADER.
|
||||
*
|
||||
* Note: this message repurposes the OPCODE field to carry the encryption algorithm.
|
||||
*/
|
||||
public class HyteraArc4EncryptionParameters extends FullLCMessage
|
||||
public class EncryptionParameters extends FullLCMessage
|
||||
{
|
||||
private static final int[] KEY_ID = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||
private static final int[] INITIALIZATION_VECTOR = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
|
||||
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] DESTINATION_GROUP = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
|
||||
71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||
//Hytera version uses CRC-CCITT with 0x9696 initial fill, bits: 80-95
|
||||
//CRC-CCITT with 0x9696 initial fill, bits: 80-95
|
||||
|
||||
private DMRTalkgroup mTalkgroup;
|
||||
private List<Identifier> mIdentifiers;
|
||||
@@ -50,9 +51,23 @@ public class HyteraArc4EncryptionParameters extends FullLCMessage
|
||||
* @param timestamp for the message
|
||||
* @param timeslot of the message
|
||||
*/
|
||||
public HyteraArc4EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
public EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
super(message, timestamp, timeslot);
|
||||
int bitErrors = CRCDMR.correctCCITT80(message, 0, 80, 0x9696);
|
||||
setValid(bitErrors < 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the default method for opcode to return a static value, because the opcode field is repurposed to
|
||||
* be the algorithm field in both Motorola and Hytera variants of this message, both carried by the Encryption
|
||||
* Header message.
|
||||
* @return full encryption parameters opcode.
|
||||
*/
|
||||
@Override
|
||||
public LCOpcode getOpcode()
|
||||
{
|
||||
return LCOpcode.FULL_ENCRYPTION_PARAMETERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,7 +90,17 @@ public class HyteraArc4EncryptionParameters extends FullLCMessage
|
||||
sb.append(" *RESERVED-BIT*");
|
||||
}
|
||||
|
||||
sb.append("FLC HYTERA ARC4/EP ENCRYPTION PARAMETERS");
|
||||
sb.append("FLC ENCRYPTION PARAMETERS");
|
||||
sb.append(" VENDOR:").append(getVendor());
|
||||
if(getAlgorithm() == EncryptionAlgorithm.UNKNOWN)
|
||||
{
|
||||
//List the algorithm (aka opcod) value for unknown algorithms
|
||||
sb.append(" ALGORITHM:").append(getOpcodeValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" ALGORITHM:").append(getAlgorithm());
|
||||
}
|
||||
sb.append(" KEY:").append(getKeyId());
|
||||
sb.append(" IV:").append(getInitializationVector());
|
||||
sb.append(" TALKGROUP:").append(getTalkgroup());
|
||||
@@ -83,6 +108,29 @@ public class HyteraArc4EncryptionParameters extends FullLCMessage
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption parameter details
|
||||
* @return details
|
||||
*/
|
||||
public String getDetails()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ENCRYPTION ALGORITHM:").append(getAlgorithm());
|
||||
sb.append(" KEY:").append(getKeyId());
|
||||
sb.append(" IV:").append(getInitializationVector());
|
||||
sb.append(" VENDOR:").append(getVendor());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption algorithm. Note: this message repurposes the OPCODE field to carry the algorithm ID.
|
||||
* @return algorithm or UNKNOWN.
|
||||
*/
|
||||
public EncryptionAlgorithm getAlgorithm()
|
||||
{
|
||||
return EncryptionAlgorithm.fromValue(getOpcodeValue());
|
||||
}
|
||||
|
||||
public DMRTalkgroup getTalkgroup()
|
||||
{
|
||||
if(mTalkgroup == null)
|
||||
@@ -30,7 +30,7 @@ public abstract class FullLCMessage extends LCMessage
|
||||
{
|
||||
private static final int ENCRYPTION_FLAG = 0;
|
||||
private static final int RESERVED = 1;
|
||||
private static final int[] OPCODE = new int[]{2, 3, 4, 5, 6, 7};
|
||||
protected static final int[] OPCODE = new int[]{2, 3, 4, 5, 6, 7};
|
||||
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,11 +37,12 @@ public abstract class HyteraFullLC extends FullLCMessage implements IServiceOpti
|
||||
protected static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||
private static final int[] PRIORITY_CALL_HASHED_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||
private static final int[] UNKNOWN_1 = new int[]{72, 73, 74, 75};
|
||||
private static final int[] UNKNOWN_2 = new int[]{76, 77, 78, 79};
|
||||
//FEC: 72-96
|
||||
//TODO: Target and Source address fields may be full 24-bit values. How do we distinguish between Hytera repeater
|
||||
//TODO: and a Hytera XPT repeater? Do the service options identify when it's XPT?
|
||||
|
||||
private ServiceOptions mServiceOptions;
|
||||
private RadioIdentifier mSourceRadio;
|
||||
protected RadioIdentifier mSourceRadio;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
@@ -110,24 +111,6 @@ public abstract class HyteraFullLC extends FullLCMessage implements IServiceOpti
|
||||
return String.format("%02X", getMessage().getInt(PRIORITY_CALL_HASHED_ADDRESS)).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown nibble one. This value remains consistent for the FLC between the Voice Header and the Terminator
|
||||
* for calls. It does not seem to have correlation to the channel/repeater number
|
||||
*/
|
||||
public int getUnknownField1()
|
||||
{
|
||||
return getMessage().getInt(UNKNOWN_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown nibble two. For each talkgroup, this field shows one value for the Voice Header and the same value but
|
||||
* bit-inverted in the Terminator.
|
||||
*/
|
||||
public int getUnknownField2()
|
||||
{
|
||||
return getMessage().getInt(UNKNOWN_2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Source radio address
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,9 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -32,6 +33,11 @@ import java.util.List;
|
||||
*/
|
||||
public class HyteraGroupVoiceChannelUser extends HyteraFullLC
|
||||
{
|
||||
protected static final int[] TALKGROUP = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||
private static final int[] SOURCE_RADIO = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
|
||||
63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||
|
||||
private TalkgroupIdentifier mTalkgroup;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
@@ -55,26 +61,11 @@ public class HyteraGroupVoiceChannelUser extends HyteraFullLC
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
|
||||
sb.append("FLC HYTERA XPT GROUP VOICE CHANNEL USER FM:");
|
||||
sb.append("FLC HYTERA GROUP VOICE CHANNEL USER FM:");
|
||||
sb.append(getSourceRadio());
|
||||
sb.append(" TO:").append(getTalkgroup());
|
||||
if(isAllChannelsBusy())
|
||||
{
|
||||
sb.append(" ALL REPEATERS BUSY");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" FREE REPEATER:").append(getFreeRepeater());
|
||||
}
|
||||
|
||||
if(hasPriorityCall())
|
||||
{
|
||||
sb.append(" PRIORITY CALL FOR:").append(getPriorityCallHashedAddress());
|
||||
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
|
||||
}
|
||||
sb.append(" ").append(getServiceOptions());
|
||||
sb.append(" UNK1:").append(getUnknownField1());
|
||||
sb.append(" UNK2:").append(getUnknownField2());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -85,12 +76,25 @@ public class HyteraGroupVoiceChannelUser extends HyteraFullLC
|
||||
{
|
||||
if(mTalkgroup == null)
|
||||
{
|
||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
|
||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
|
||||
}
|
||||
|
||||
return mTalkgroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source radio address
|
||||
*/
|
||||
public RadioIdentifier getSourceRadio()
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -73,8 +72,6 @@ public class HyteraTerminator extends HyteraFullLC
|
||||
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
|
||||
}
|
||||
sb.append(" ").append(getServiceOptions());
|
||||
sb.append(" UNK1:").append(getUnknownField1());
|
||||
sb.append(" UNK2:").append(getUnknownField2());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -72,8 +71,6 @@ public class HyteraUnitToUnitVoiceChannelUser extends HyteraFullLC
|
||||
sb.append(" PRIORITY CALL FOR:").append(getPriorityCallHashedAddress());
|
||||
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
|
||||
}
|
||||
sb.append(" UNK1:").append(getUnknownField1());
|
||||
sb.append(" UNK2:").append(getUnknownField2());
|
||||
sb.append(" ").append(getServiceOptions());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -176,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[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return getRestChannel().getLogicalSlotNumbers();
|
||||
return getRestChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,12 +189,6 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
if(getRestChannel().getValue() == timeslotFrequency.getNumber())
|
||||
{
|
||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getRestChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Motorola ARC4/EP Encryption Parameters
|
||||
* <p>
|
||||
* Note: observed as FLC payload for a PI_HEADER slot type.
|
||||
* Note: observed on a possible Hytera (clone) system that was configured as IP Site Connect compatible.
|
||||
*/
|
||||
public class MotorolaArc4EncryptionParameters extends FullLCMessage
|
||||
{
|
||||
private static final int[] KEY_ID = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||
private static final int[] INITIALIZATION_VECTOR = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
|
||||
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] DESTINATION_GROUP = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||
//Motorola Reed Solomon FEC bits: 72-95
|
||||
|
||||
private DMRTalkgroup mTalkgroup;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param message bits
|
||||
* @param timestamp for the message
|
||||
* @param timeslot of the message
|
||||
*/
|
||||
public MotorolaArc4EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
super(message, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
|
||||
if(isEncrypted())
|
||||
{
|
||||
sb.append(" *ENCRYPTED*");
|
||||
}
|
||||
|
||||
if(isReservedBitSet())
|
||||
{
|
||||
sb.append(" *RESERVED-BIT*");
|
||||
}
|
||||
|
||||
sb.append("FLC MOTOROLA ARC4/EP ENCRYPTION PARAMETERS");
|
||||
sb.append(" KEY:").append(getKeyId());
|
||||
sb.append(" IV:").append(getInitializationVector());
|
||||
sb.append(" TALKGROUP:").append(getTalkgroup());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public DMRTalkgroup getTalkgroup()
|
||||
{
|
||||
if(mTalkgroup == null)
|
||||
{
|
||||
mTalkgroup = new DMRTalkgroup(getMessage().getInt(DESTINATION_GROUP));
|
||||
}
|
||||
|
||||
return mTalkgroup;
|
||||
}
|
||||
|
||||
public int getKeyId()
|
||||
{
|
||||
return getMessage().getInt(KEY_ID);
|
||||
}
|
||||
|
||||
public String getInitializationVector()
|
||||
{
|
||||
return getMessage().getHex(INITIALIZATION_VECTOR, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkgroup());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -105,9 +105,9 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
||||
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
|
||||
*/
|
||||
@Override
|
||||
public int[] getLogicalSlotNumbers()
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return getRestChannel().getLogicalSlotNumbers();
|
||||
return getRestChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,13 +117,7 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
{
|
||||
if(getRestChannel().getValue() == timeslotFrequency.getNumber())
|
||||
{
|
||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
||||
}
|
||||
}
|
||||
getRestChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,7 +24,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -33,12 +35,12 @@ import java.util.List;
|
||||
*/
|
||||
public class PacketSequence
|
||||
{
|
||||
private int mTimeslot;
|
||||
private final int mTimeslot;
|
||||
private PacketSequenceHeader mPacketSequenceHeader;
|
||||
private UDTHeader mUDTHeader;
|
||||
private ProprietaryDataHeader mProprietaryDataHeader;
|
||||
private List<Preamble> mPreambles = new ArrayList<>();
|
||||
private List<DataBlock> mDataBlocks = new ArrayList<>();
|
||||
private final List<Preamble> mPreambles = new ArrayList<>();
|
||||
private final List<DataBlock> mDataBlocks = new ArrayList<>();
|
||||
|
||||
public PacketSequence(int timeslot)
|
||||
{
|
||||
@@ -96,7 +98,19 @@ public class PacketSequence
|
||||
*/
|
||||
public boolean isEncrypted()
|
||||
{
|
||||
return hasProprietaryDataHeader() && getProprietaryDataHeader() instanceof MotorolaDataEncryptionHeader;
|
||||
if(hasProprietaryDataHeader())
|
||||
{
|
||||
if(getProprietaryDataHeader() instanceof MotorolaDataEncryptionHeader)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(getProprietaryDataHeader() instanceof HyteraDataEncryptionHeader hdeh)
|
||||
{
|
||||
return hdeh.getAlgorithm() != EncryptionAlgorithm.NO_ENCRYPTION;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getTimeslot()
|
||||
|
||||
@@ -25,17 +25,19 @@ import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
|
||||
import io.github.dsheirer.module.decode.ip.DefinedShortDataPacket;
|
||||
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenHeader;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.shortdata.HyteraShortDataPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Header;
|
||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
||||
@@ -167,19 +169,30 @@ public class PacketSequenceMessageFactory
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
}
|
||||
}
|
||||
else if(secondaryHeader instanceof HyteraProprietaryDataHeader)
|
||||
else if(secondaryHeader instanceof HyteraDataEncryptionHeader hdeh)
|
||||
{
|
||||
HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
|
||||
ServiceAccessPoint sap = hdeh.getServiceAccessPoint();
|
||||
|
||||
if(hyteraTokenHeader.isSMSMessage())
|
||||
switch(sap)
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new HyteraSmsPacket(hyteraTokenHeader), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new HyteraUnknownPacket(hyteraTokenHeader), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
case SHORT_DATA:
|
||||
return new DMRPacketMessage(packetSequence, new HyteraShortDataPacket(packetSequence, packet), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
case PROPRIETARY_DATA:
|
||||
HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
|
||||
if(hyteraTokenHeader.isSMSMessage())
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new HyteraSmsPacket(hyteraTokenHeader), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new HyteraUnknownPacket(hyteraTokenHeader), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
}
|
||||
default:
|
||||
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
}
|
||||
}
|
||||
else if(secondaryHeader instanceof MotorolaDataEncryptionHeader &&
|
||||
@@ -231,8 +244,19 @@ public class PacketSequenceMessageFactory
|
||||
*/
|
||||
public static IMessage createDefinedShortData(PacketSequence packetSequence, CorrectedBinaryMessage packet)
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new DefinedShortDataPacket(packet, 0), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
//Test to see if this is Hytera Radio Registration Service (RRS)
|
||||
HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
|
||||
|
||||
if(hyteraTokenHeader.isRRSMessage())
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new HyteraRrsPacket(hyteraTokenHeader), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new DefinedShortDataPacket(packet, 0), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,7 +272,7 @@ public class PacketSequenceMessageFactory
|
||||
|
||||
if(sequence.hasProprietaryDataHeader())
|
||||
{
|
||||
CorrectedBinaryMessage prefix = ((ProprietaryDataHeader)sequence.getProprietaryDataHeader()).getPacketPrefix();
|
||||
CorrectedBinaryMessage prefix = sequence.getProprietaryDataHeader().getPacketPrefix();
|
||||
|
||||
if(prefix != null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.type;
|
||||
|
||||
/**
|
||||
* Enumeration of encryption algorithms
|
||||
*/
|
||||
public enum EncryptionAlgorithm
|
||||
{
|
||||
NO_ENCRYPTION(0x00, "NO ENCRYPTION"),
|
||||
HYTERA_BASIC_PRIVACY(0x01, "HYTERA BP"),
|
||||
HYTERA_ENHANCED_PRIVACY(0x02, "HYTERA RC4/EP"),
|
||||
DMRA_RC4(0x21, "DMRA RC4/EP"),
|
||||
DMRA_AES128(0x24, "DMRA AES128"),
|
||||
DMRA_AES256(0x25, "DMRA AES256" ),
|
||||
HYTERA_ENHANCED_PRIVACY_2(0x26, "HYTERA RC4/EP"),
|
||||
UNKNOWN(-1, "UNKNOWN");
|
||||
|
||||
private final int mValue;
|
||||
private final String mLabel;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param value for the algorithm
|
||||
* @param label to display
|
||||
*/
|
||||
EncryptionAlgorithm(int value, String label)
|
||||
{
|
||||
mValue = value;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Numeric value for the algorithm
|
||||
* @return value.
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to lookup the encryption from the value.
|
||||
* @param value to lookup
|
||||
* @return matching entry or UNKNOWN
|
||||
*/
|
||||
public static EncryptionAlgorithm fromValue(int value)
|
||||
{
|
||||
for(EncryptionAlgorithm algorithm: EncryptionAlgorithm.values())
|
||||
{
|
||||
if(algorithm.getValue() == value)
|
||||
{
|
||||
return algorithm;
|
||||
}
|
||||
}
|
||||
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ import io.github.dsheirer.edac.BPTC_16_2;
|
||||
import io.github.dsheirer.edac.Golay24;
|
||||
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.Arc4EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NonStandardShortBurst;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NullShortBurst;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurst;
|
||||
@@ -38,8 +38,8 @@ import io.github.dsheirer.module.decode.dmr.message.voice.embedded.UnknownShortB
|
||||
* Monitors audio call voice frame messaging to detect encrypted audio calls and extract the encryption parameters
|
||||
* that are embedded into the six voice frames that comprise a voice super-frame.
|
||||
* <p>
|
||||
* See patent: https://patents.google.com/patent/EP2347540B1/en - embedding encryption parameters in voice super frame
|
||||
* See patent: https://patents.google.com/patent/US8271009B2 - TXI - interrupting voice transmissions
|
||||
* See patent: <a href="https://patents.google.com/patent/EP2347540B1/en">...</a> - embedding encryption parameters in voice super frame
|
||||
* See patent: <a href="https://patents.google.com/patent/US8271009B2">...</a> - TXI - interrupting voice transmissions
|
||||
*/
|
||||
public class VoiceSuperFrameProcessor
|
||||
{
|
||||
@@ -47,8 +47,6 @@ public class VoiceSuperFrameProcessor
|
||||
33, 34, 35, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] CRC4 = new int[]{56, 57, 58, 59};
|
||||
private boolean mEncrypted = false;
|
||||
private int mAlgorithm;
|
||||
private int mKey;
|
||||
private byte[] mFragmentA;
|
||||
private byte[] mFragmentB;
|
||||
private byte[] mFragmentC;
|
||||
@@ -83,8 +81,6 @@ public class VoiceSuperFrameProcessor
|
||||
|
||||
private void softReset()
|
||||
{
|
||||
mAlgorithm = 0;
|
||||
mKey = 0;
|
||||
mFragmentA = null;
|
||||
mFragmentB = null;
|
||||
mFragmentC = null;
|
||||
@@ -124,9 +120,6 @@ public class VoiceSuperFrameProcessor
|
||||
{
|
||||
if(voiceMessage instanceof VoiceEMBMessage voiceEMB)
|
||||
{
|
||||
boolean valid = voiceEMB.getEMB().isValid();
|
||||
boolean encrypted = voiceEMB.getEMB().isEncrypted();
|
||||
|
||||
if(voiceEMB.getEMB().isValid() && voiceEMB.getEMB().isEncrypted())
|
||||
{
|
||||
mEncrypted = true;
|
||||
@@ -175,7 +168,6 @@ public class VoiceSuperFrameProcessor
|
||||
* initialization vector to return a parameters object.
|
||||
*
|
||||
* @param frameFFragment containing the short-burst FLC fragment
|
||||
* @param iv that was previously extracted from the voice super-frame
|
||||
* @return embedded parameters.
|
||||
*/
|
||||
private ShortBurst extractShortBurst(BinaryMessage frameFFragment)
|
||||
@@ -191,7 +183,7 @@ public class VoiceSuperFrameProcessor
|
||||
return switch(opcode)
|
||||
{
|
||||
case NULL -> new NullShortBurst(decoded);
|
||||
case ARC4_ENCRYPTION -> new Arc4EncryptionParameters(decoded);
|
||||
case ARC4_ENCRYPTION, AES128_ENCRYPTION, AES256_ENCRYPTION -> new EncryptionParameters(decoded);
|
||||
case TXI_DELAY -> new TransmitInterrupt(decoded);
|
||||
default -> new UnknownShortBurst(decoded);
|
||||
};
|
||||
@@ -233,11 +225,11 @@ public class VoiceSuperFrameProcessor
|
||||
|
||||
if(passes)
|
||||
{
|
||||
return String.format("%04X", iv);
|
||||
return String.format("%08X", iv);
|
||||
}
|
||||
else
|
||||
{
|
||||
return String.format("%04X", iv) + "(CRC-FAIL " + check1 + "/" + check2 + "/" + check3 + "/" +
|
||||
return String.format("%08X", iv) + "(CRC-FAIL " + check1 + "/" + check2 + "/" + check3 + "/" +
|
||||
reassembled.getCorrectedBitCount() + ")";
|
||||
}
|
||||
}
|
||||
@@ -262,10 +254,10 @@ public class VoiceSuperFrameProcessor
|
||||
*/
|
||||
public static boolean crc4(int value, int crc)
|
||||
{
|
||||
long checksum = (value & 0x0FFFFFFFFl) << 4;
|
||||
long checksum = (value & 0x0FFFFFFFFL) << 4;
|
||||
checksum ^= 0xF; //Initial fill
|
||||
long polynomial = 0x013l << 31;
|
||||
long checkBit = 0x1l << 35;
|
||||
long polynomial = 0x013L << 31;
|
||||
long checkBit = 0x1L << 35;
|
||||
|
||||
for(int x = 31; x >= 0; x--)
|
||||
{
|
||||
|
||||
@@ -20,11 +20,12 @@
|
||||
package io.github.dsheirer.module.decode.dmr.message.voice.embedded;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
|
||||
|
||||
/**
|
||||
* Encryption parameters short burst payload.
|
||||
*/
|
||||
public class Arc4EncryptionParameters extends ShortBurst
|
||||
public class EncryptionParameters extends ShortBurst
|
||||
{
|
||||
private static final int[] KEY = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||
private static final int[] ALGORITHM = new int[]{8, 9, 10};
|
||||
@@ -34,7 +35,7 @@ public class Arc4EncryptionParameters extends ShortBurst
|
||||
*
|
||||
* @param message containing the de-interleaved and error-corrected short burst payload.
|
||||
*/
|
||||
public Arc4EncryptionParameters(CorrectedBinaryMessage message)
|
||||
public EncryptionParameters(CorrectedBinaryMessage message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
@@ -49,31 +50,45 @@ public class Arc4EncryptionParameters extends ShortBurst
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption algorithm
|
||||
* @return algorithm (0 - 7). 1 = ARC4
|
||||
* Encryption algorithm value
|
||||
* @return algorithm (0 - 7).
|
||||
*/
|
||||
public int getAlgorithmValue()
|
||||
{
|
||||
return getMessage().getInt(ALGORITHM);
|
||||
}
|
||||
|
||||
public String getAlgorithm()
|
||||
/**
|
||||
* Encryption algorithm.
|
||||
* @return algorithm
|
||||
*/
|
||||
public EncryptionAlgorithm getAlgorithm()
|
||||
{
|
||||
int algorithm = getAlgorithmValue();
|
||||
|
||||
if(algorithm == 1)
|
||||
return switch(algorithm)
|
||||
{
|
||||
return "EP/ARC4";
|
||||
}
|
||||
|
||||
return "ALGORITHM:" + algorithm;
|
||||
case 1 -> EncryptionAlgorithm.DMRA_RC4;
|
||||
case 4 -> EncryptionAlgorithm.DMRA_AES128;
|
||||
case 5 -> EncryptionAlgorithm.DMRA_AES256;
|
||||
default -> EncryptionAlgorithm.UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ENCRYPTION:").append(getAlgorithm());
|
||||
|
||||
sb.append("ENCRYPTION ALGORITHM:");
|
||||
if(getAlgorithm() == EncryptionAlgorithm.UNKNOWN)
|
||||
{
|
||||
sb.append(getAlgorithmValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(getAlgorithm());
|
||||
}
|
||||
sb.append(" KEY:").append(getKey());
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -21,17 +21,18 @@ package io.github.dsheirer.module.decode.dmr.message.voice.embedded;
|
||||
|
||||
/**
|
||||
* Short Burst opcode
|
||||
*
|
||||
* See: https://patents.google.com/patent/US8271009B2 Page 12
|
||||
* See: <a href="https://patents.google.com/patent/US8271009B2">...</a> Page 12
|
||||
*/
|
||||
public enum ShortBurstOpcode
|
||||
{
|
||||
NULL(0),
|
||||
ARC4_ENCRYPTION(1),
|
||||
TXI_DELAY(3),
|
||||
AES128_ENCRYPTION(4),
|
||||
AES256_ENCRYPTION(5),
|
||||
UNKNOWN(-1);
|
||||
|
||||
private int mValue;
|
||||
private final int mValue;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -221,7 +221,7 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
sb.append(",");
|
||||
}
|
||||
|
||||
if(identifier.getForm() == Form.TALKGROUP)
|
||||
if(identifier.getForm() == Form.TALKGROUP || identifier.getForm() == Form.RADIO || identifier.getForm() == Form.PATCH_GROUP)
|
||||
{
|
||||
sb.append(mUserPreferences.getTalkgroupFormatPreference().format(identifier));
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ public enum DecodeEventType
|
||||
PAGE("Page"),
|
||||
QUERY("Query"),
|
||||
RADIO_CHECK("Radio Check"),
|
||||
RADIO_REGISTRATION_SERVICE("Hytera RRS"),
|
||||
REGISTER("Register"),
|
||||
REGISTER_ESN("ESN"),
|
||||
REQUEST("Request"),
|
||||
@@ -78,9 +79,10 @@ public enum DecodeEventType
|
||||
STATUS("Status"),
|
||||
UDP_PACKET("UDP/IP Packet"),
|
||||
UNKNOWN_PACKET("Unknown Packet"),
|
||||
XCMP("Motorola XCMP"),
|
||||
UNKNOWN("Unknown");
|
||||
|
||||
private String mLabel;
|
||||
private final String mLabel;
|
||||
|
||||
/**
|
||||
* Encrypted voice call event types for filtering
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.ip.hytera.rrs;
|
||||
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.DestinationId;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraToken;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenHeader;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenType;
|
||||
import io.github.dsheirer.module.decode.p25.identifier.ipv4.DMRIpAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hytera SDS Long Data Message with SMS encoded payload
|
||||
*/
|
||||
public class HyteraRrsPacket implements IPacket
|
||||
{
|
||||
private HyteraTokenHeader mHeader;
|
||||
private Identifier mDestination;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||
*
|
||||
* @param header to the packet within the message
|
||||
*/
|
||||
public HyteraRrsPacket(HyteraTokenHeader header)
|
||||
{
|
||||
mHeader = header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("HYTERA RRS REGISTER");
|
||||
if(hasDestination())
|
||||
{
|
||||
sb.append(" RADIO:").append(getDestination());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HyteraTokenHeader getHeader()
|
||||
{
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destination radio identifier for the SMS message
|
||||
*/
|
||||
public Identifier getDestination()
|
||||
{
|
||||
if(mDestination == null)
|
||||
{
|
||||
HyteraToken destinationToken = getHeader().getTokenByType(HyteraTokenType.ID_DESTINATION);
|
||||
|
||||
if(destinationToken instanceof DestinationId destinationId)
|
||||
{
|
||||
if(((DestinationId) destinationToken).isIp())
|
||||
{
|
||||
mDestination = DMRIpAddress.createFrom(((DestinationId) destinationToken).getId());
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestination = DMRRadio.createTo(destinationId.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mDestination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the SMS message has a destination identifier
|
||||
*/
|
||||
public boolean hasDestination()
|
||||
{
|
||||
return getDestination() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
if(hasDestination())
|
||||
{
|
||||
mIdentifiers.add(getDestination());
|
||||
}
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPacket getPayload()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPayload()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
package io.github.dsheirer.module.decode.ip.hytera.sds;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.ipv4.IPV4Address;
|
||||
|
||||
/**
|
||||
* Destination Identifier
|
||||
@@ -45,10 +46,19 @@ public class DestinationId extends HyteraToken
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("DESTINATION:").append(getId());
|
||||
sb.append("DESTINATION:").append(getIdFormatted());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is an IP address (true) or a Radio ID (false).
|
||||
* @return true if IP
|
||||
*/
|
||||
public boolean isIp()
|
||||
{
|
||||
return getId() > 0x00FFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the content field in bytes.
|
||||
* @return content field length.
|
||||
@@ -69,6 +79,18 @@ public class DestinationId extends HyteraToken
|
||||
return mMessage.getInt(ID);
|
||||
}
|
||||
|
||||
public String getIdFormatted()
|
||||
{
|
||||
if(isIp())
|
||||
{
|
||||
return new IPV4Address(getId()).toString() + " [" + (getId() & 0xFFFFFF) + "]";
|
||||
}
|
||||
else
|
||||
{
|
||||
return String.valueOf(getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
HyteraTokenType getTokenType()
|
||||
{
|
||||
|
||||
@@ -37,11 +37,13 @@ public class HyteraTokenFactory
|
||||
return switch(token)
|
||||
{
|
||||
case ID_DESTINATION -> new DestinationId(message);
|
||||
case ENCODING -> new Encoding(message);
|
||||
case ID_MESSAGE -> new MessageId(message);
|
||||
case PAYLOAD -> new Payload(message);
|
||||
case MESSAGE_HEADER -> new MessageHeader(message);
|
||||
case ID_SOURCE -> new SourceId(message);
|
||||
case ENCODING -> new Encoding(message);
|
||||
case PAYLOAD -> new Payload(message);
|
||||
case RRS_UNKNOWN -> new RrsUnknown(message);
|
||||
case TEXT_MESSAGE_SERVICE_HEADER -> new TmsMessageHeader(message);
|
||||
case RADIO_REGISTRATION_SERVICE_HEADER -> new RrsMessageHeader(message);
|
||||
default -> new Unknown(message);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,7 +72,15 @@ public class HyteraTokenHeader implements IHeader
|
||||
*/
|
||||
public boolean isSMSMessage()
|
||||
{
|
||||
return hasTokens(HyteraTokenType.MESSAGE_HEADER, HyteraTokenType.ENCODING, HyteraTokenType.PAYLOAD);
|
||||
return hasTokens(HyteraTokenType.TEXT_MESSAGE_SERVICE_HEADER, HyteraTokenType.ENCODING, HyteraTokenType.PAYLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this header has RRS service message.
|
||||
*/
|
||||
public boolean isRRSMessage()
|
||||
{
|
||||
return hasTokens(HyteraTokenType.RADIO_REGISTRATION_SERVICE_HEADER, HyteraTokenType.ID_DESTINATION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,7 +161,7 @@ public class HyteraTokenHeader implements IHeader
|
||||
length += contentLength * 8; //Actual content
|
||||
}
|
||||
|
||||
HyteraToken field = HyteraTokenFactory.getField(token, mMessage.getSubMessage(offset, offset + length + 1));
|
||||
HyteraToken field = HyteraTokenFactory.getField(token, mMessage.getSubMessage(offset, offset + length));
|
||||
offset += length;
|
||||
mParsedTokens.add(field);
|
||||
}
|
||||
|
||||
@@ -29,13 +29,15 @@ package io.github.dsheirer.module.decode.ip.hytera.sds;
|
||||
*/
|
||||
public enum HyteraTokenType
|
||||
{
|
||||
//Note: this may be the message type, but is definitely followed by 4-byte total length field. Need more examples
|
||||
MESSAGE_HEADER(0x0201, 4), //First 2 bytes are total message length, 2nd 2 bytes = unknown
|
||||
RADIO_REGISTRATION_SERVICE_HEADER(0x0200, 1), //Total message length
|
||||
TEXT_MESSAGE_SERVICE_HEADER(0x0201, 4), //First 2 bytes are total message length, 2nd 2 bytes = unknown
|
||||
|
||||
ID_MESSAGE(0x0001, -1),
|
||||
ID_SOURCE(0x0002, -1),
|
||||
ID_DESTINATION(0x0003, -1),
|
||||
ENCODING(0x0101, -1),
|
||||
PAYLOAD(0x0102, -1),
|
||||
RRS_UNKNOWN(0x0703, 0),
|
||||
UNKNOWN(-1, -1);
|
||||
|
||||
private int mOpcode;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.ip.hytera.sds;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Radio Registration Service (RRS) Message Header
|
||||
*/
|
||||
public class RrsMessageHeader extends HyteraToken
|
||||
{
|
||||
private static int[] MESSAGE_LENGTH_BCD_TENS = new int[]{16, 17, 18, 19};
|
||||
private static int[] MESSAGE_LENGTH_BCD_ONES = new int[]{20, 21, 22, 23};
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message containing both the token, run-length, and content
|
||||
*/
|
||||
public RrsMessageHeader(CorrectedBinaryMessage message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("RRS HEADER LENGTH:").append(getMessageLength());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
HyteraTokenType getTokenType()
|
||||
{
|
||||
return HyteraTokenType.RADIO_REGISTRATION_SERVICE_HEADER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overall message length in bytes
|
||||
* @return message length.
|
||||
*/
|
||||
public int getMessageLength()
|
||||
{
|
||||
int length = mMessage.getInt(MESSAGE_LENGTH_BCD_TENS) * 10;
|
||||
length += mMessage.getInt(MESSAGE_LENGTH_BCD_ONES);
|
||||
return length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.ip.hytera.sds;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Unknown RRS field parsed from a Hytera Radio Registration Service (RRS) message
|
||||
*/
|
||||
public class RrsUnknown extends HyteraToken
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message containing both the token, run-length, and content
|
||||
*/
|
||||
public RrsUnknown(CorrectedBinaryMessage message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "RRS UNKNOWN TOKEN";
|
||||
}
|
||||
|
||||
@Override
|
||||
HyteraTokenType getTokenType()
|
||||
{
|
||||
return HyteraTokenType.RRS_UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -22,16 +22,19 @@ package io.github.dsheirer.module.decode.ip.hytera.sds;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Message Header
|
||||
*
|
||||
* Text Message Service (TMS) Message Header
|
||||
* <p>
|
||||
* Initial token that specifies the overall message length and some other unidentified bit fields.
|
||||
*
|
||||
* <p>
|
||||
* Note: I've only confirmed this is the start token for long SMS messages, but it may also be the start token for
|
||||
* other packet data messages like GPS or any other packet overlay application.
|
||||
*/
|
||||
public class MessageHeader extends HyteraToken
|
||||
public class TmsMessageHeader extends HyteraToken
|
||||
{
|
||||
private static int[] MESSAGE_LENGTH = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static int[] MESSAGE_LENGTH_BCD_THOUSANDS = new int[]{16, 17, 18, 19};
|
||||
private static int[] MESSAGE_LENGTH_BCD_HUNDREDS = new int[]{20, 21, 22, 23};
|
||||
private static int[] MESSAGE_LENGTH_BCD_TENS = new int[]{24, 25, 26, 27};
|
||||
private static int[] MESSAGE_LENGTH_BCD_ONES = new int[]{28, 29, 30, 31};
|
||||
private static int[] MESSAGE_FLAGS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||
|
||||
/**
|
||||
@@ -39,7 +42,7 @@ public class MessageHeader extends HyteraToken
|
||||
*
|
||||
* @param message containing both the token, run-length, and content
|
||||
*/
|
||||
public MessageHeader(CorrectedBinaryMessage message)
|
||||
public TmsMessageHeader(CorrectedBinaryMessage message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
@@ -56,7 +59,7 @@ public class MessageHeader extends HyteraToken
|
||||
@Override
|
||||
HyteraTokenType getTokenType()
|
||||
{
|
||||
return HyteraTokenType.MESSAGE_HEADER;
|
||||
return HyteraTokenType.TEXT_MESSAGE_SERVICE_HEADER;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,6 +68,7 @@ public class MessageHeader extends HyteraToken
|
||||
* - Priority/Flash (boolean)
|
||||
* - Encryption Type (enum of at least 4 values, ie 2+ bits)
|
||||
* - Emergency (boolean)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getFlags()
|
||||
@@ -78,6 +82,10 @@ public class MessageHeader extends HyteraToken
|
||||
*/
|
||||
public int getMessageLength()
|
||||
{
|
||||
return mMessage.getInt(MESSAGE_LENGTH);
|
||||
int length = mMessage.getInt(MESSAGE_LENGTH_BCD_THOUSANDS) * 1000;
|
||||
length += mMessage.getInt(MESSAGE_LENGTH_BCD_HUNDREDS) * 100;
|
||||
length += mMessage.getInt(MESSAGE_LENGTH_BCD_TENS) * 10;
|
||||
length += mMessage.getInt(MESSAGE_LENGTH_BCD_ONES);
|
||||
return length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.ip.hytera.shortdata;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequence;
|
||||
import io.github.dsheirer.module.decode.ip.IHeader;
|
||||
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hytera Short Data Packet Sequence
|
||||
*/
|
||||
public class HyteraShortDataPacket implements IPacket
|
||||
{
|
||||
private final PacketSequence mPacketSequence;
|
||||
private CorrectedBinaryMessage mMessage;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param packetSequence containing the headers and data blocks
|
||||
* @param payload containing the reconstructed short data message from the data blocks.
|
||||
*/
|
||||
public HyteraShortDataPacket(PacketSequence packetSequence, CorrectedBinaryMessage payload)
|
||||
{
|
||||
mPacketSequence = packetSequence;
|
||||
mMessage = payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CC:").append(getPacketSequence().getPacketSequenceHeader().getSlotType().getColorCode());
|
||||
sb.append(" HYTERA SHORT DATA MESSAGE FM:").append(getPacketSequence().getPacketSequenceHeader().getSourceLLID());
|
||||
sb.append(" TO:").append(getPacketSequence().getPacketSequenceHeader().getDestinationLLID());
|
||||
|
||||
if(getPacketSequence().isEncrypted())
|
||||
{
|
||||
HyteraDataEncryptionHeader hdeh = (HyteraDataEncryptionHeader)getPacketSequence().getProprietaryDataHeader();
|
||||
sb.append(" ENCRYPTED ALGORITHM:").append(hdeh.getAlgorithm());
|
||||
sb.append(" KEY:").append(hdeh.getKeyId());
|
||||
sb.append(" IV:").append(hdeh.getIV());
|
||||
}
|
||||
|
||||
sb.append(" SHORT DATA:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHeader getHeader()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPacket getPayload()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPayload()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload message
|
||||
* @return message
|
||||
*/
|
||||
public CorrectedBinaryMessage getMessage()
|
||||
{
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Captured packet sequence
|
||||
* @return packet sequence
|
||||
*/
|
||||
public PacketSequence getPacketSequence()
|
||||
{
|
||||
return mPacketSequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this packet is encrypted, meaning it contains a Hytera Data Encryption Header.
|
||||
* @return true if encrypted
|
||||
*/
|
||||
public boolean isEncrypted()
|
||||
{
|
||||
return getPacketSequence().isEncrypted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return getPacketSequence().getPacketSequenceHeader().getIdentifiers();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.p25.identifier.ipv4;
|
||||
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.ipv4.IPV4Address;
|
||||
import io.github.dsheirer.identifier.ipv4.IPV4Identifier;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* DMR IPV4 Address
|
||||
*/
|
||||
public class DMRIpAddress extends IPV4Identifier
|
||||
{
|
||||
public DMRIpAddress(IPV4Address ipAddress, Role role)
|
||||
{
|
||||
super(ipAddress, role);
|
||||
}
|
||||
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.DMR;
|
||||
}
|
||||
|
||||
public static DMRIpAddress createTo(int value)
|
||||
{
|
||||
return new DMRIpAddress(new IPV4Address(value), Role.TO);
|
||||
}
|
||||
|
||||
public static DMRIpAddress createFrom(int value)
|
||||
{
|
||||
return new DMRIpAddress(new IPV4Address(value), Role.FROM);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user