mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#1994 P25 Phase1 missing subsequent calls audio when multiple conversations on the same traffic channel. (#1995)
Fixed issue where sdrtrunk was not checking HDU and LDU2 encryption sequences as valid before assessing the encryption state of a call. Updated P25P1 audio module to check the valid flag for both the HDU and the LDU2 encryption segment before assigning encryption state. Now caches all LDU messages until encryption state is determined. Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -19,11 +19,11 @@
|
||||
|
||||
package io.github.dsheirer.gui.viewer;
|
||||
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventSnapshot;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -37,6 +37,7 @@ public class MessagePackage
|
||||
private List<DecoderStateEvent> mDecoderStateEvents = new ArrayList<>();
|
||||
private List<DecodeEventSnapshot> mDecodeEvents = new ArrayList<>();
|
||||
private ChannelStartProcessingRequest mChannelStartProcessingRequest;
|
||||
private AudioSegment mAudioSegment;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -152,8 +153,39 @@ public class MessagePackage
|
||||
return mChannelStartProcessingRequest == null ? 0 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count (0 or 1) of audio segment.
|
||||
*/
|
||||
public int getAudioSegmentCount()
|
||||
{
|
||||
return mAudioSegment == null ? 0 : 1;
|
||||
}
|
||||
|
||||
public ChannelStartProcessingRequest getChannelStartProcessingRequest()
|
||||
{
|
||||
return mChannelStartProcessingRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generated audio segment.
|
||||
* @return segment or null.
|
||||
*/
|
||||
public AudioSegment getAudioSegment()
|
||||
{
|
||||
return mAudioSegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the audio segment to the package
|
||||
* @param audioSegment to add
|
||||
*/
|
||||
public void add(AudioSegment audioSegment)
|
||||
{
|
||||
if(mAudioSegment != null)
|
||||
{
|
||||
throw new IllegalStateException("AudioSegment already set");
|
||||
}
|
||||
|
||||
mAudioSegment = audioSegment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ package io.github.dsheirer.gui.viewer;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventSnapshot;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
@@ -38,6 +40,7 @@ public class MessagePackageViewer extends VBox
|
||||
private TableView<DecoderStateEvent> mDecoderStateEventTableView;
|
||||
private TableView<DecodeEventSnapshot> mDecodeEventTableView;
|
||||
private IdentifierCollectionViewer mIdentifierCollectionViewer;
|
||||
private IdentifierCollectionViewer mAudioSegmentIdCollectionViewer;
|
||||
private ChannelStartProcessingRequestViewer mChannelStartProcessingRequestViewer;
|
||||
|
||||
/**
|
||||
@@ -63,8 +66,14 @@ public class MessagePackageViewer extends VBox
|
||||
GridPane.setHgrow(getDecodeEventTableView(), Priority.ALWAYS);
|
||||
gridPane.add(getDecodeEventTableView(), 1, 2);
|
||||
|
||||
gridPane.add(new Label("Channel Start Processing Request"), 0, 3);
|
||||
gridPane.add(getChannelStartProcessingRequestViewer(), 0, 4);
|
||||
TabPane tabPane = new TabPane();
|
||||
Tab startTab = new Tab("Channel Start Processing Request");
|
||||
startTab.setContent(getChannelStartProcessingRequestViewer());
|
||||
Tab audioTab = new Tab("Audio Segment");
|
||||
audioTab.setContent(getAudioSegmentIdCollectionViewer());
|
||||
tabPane.getTabs().addAll(audioTab, startTab);
|
||||
|
||||
gridPane.add(tabPane, 0, 4);
|
||||
|
||||
getIdentifierCollectionViewer().setPrefHeight(120);
|
||||
gridPane.add(new Label("Selected Decode Event Identifiers"), 1, 3);
|
||||
@@ -96,6 +105,15 @@ public class MessagePackageViewer extends VBox
|
||||
{
|
||||
getDecodeEventTableView().getSelectionModel().select(0);
|
||||
}
|
||||
|
||||
if(messagePackage.getAudioSegment() != null)
|
||||
{
|
||||
getAudioSegmentIdCollectionViewer().set(messagePackage.getAudioSegment().getIdentifierCollection());
|
||||
}
|
||||
else
|
||||
{
|
||||
getAudioSegmentIdCollectionViewer().set(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +127,16 @@ public class MessagePackageViewer extends VBox
|
||||
return mIdentifierCollectionViewer;
|
||||
}
|
||||
|
||||
private IdentifierCollectionViewer getAudioSegmentIdCollectionViewer()
|
||||
{
|
||||
if(mAudioSegmentIdCollectionViewer == null)
|
||||
{
|
||||
mAudioSegmentIdCollectionViewer = new IdentifierCollectionViewer();
|
||||
}
|
||||
|
||||
return mAudioSegmentIdCollectionViewer;
|
||||
}
|
||||
|
||||
private Label getMessageLabel()
|
||||
{
|
||||
if(mMessageLabel == null)
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
package io.github.dsheirer.gui.viewer;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventSnapshot;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEventListener;
|
||||
|
||||
/**
|
||||
* Utility for combining a message and decoder state events.
|
||||
@@ -42,6 +42,18 @@ public class MessagePackager
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an audio segment.
|
||||
* @param audioSegment to add
|
||||
*/
|
||||
public void add(AudioSegment audioSegment)
|
||||
{
|
||||
if(mMessagePackage != null)
|
||||
{
|
||||
mMessagePackage.add(audioSegment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the message and creates a new MessageWithEvents instance, wrapping the message, ready to also receive any
|
||||
* decode events and decoder state events. The previous message with events is overwritten.
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package io.github.dsheirer.gui.viewer;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
@@ -51,6 +52,7 @@ public class MessageRecordingViewer extends VBox
|
||||
private int mTabCounterDmr = 1;
|
||||
private int mTabCounterP25P1 = 1;
|
||||
private int mTabCounterP25P2 = 1;
|
||||
private UserPreferences mUserPreferences = new UserPreferences();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -77,7 +79,7 @@ public class MessageRecordingViewer extends VBox
|
||||
});
|
||||
MenuItem p25p1MenuItem = new MenuItem("P25 Phase 1");
|
||||
p25p1MenuItem.onActionProperty().set(event -> {
|
||||
Tab tab = new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer());
|
||||
Tab tab = new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer(mUserPreferences));
|
||||
getTabPane().getTabs().add(tab);
|
||||
getTabPane().getSelectionModel().select(tab);
|
||||
});
|
||||
@@ -108,7 +110,7 @@ public class MessageRecordingViewer extends VBox
|
||||
mTabPane = new TabPane();
|
||||
mTabPane.setMaxHeight(Double.MAX_VALUE);
|
||||
mTabPane.getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer()));
|
||||
mTabPane.getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer()));
|
||||
mTabPane.getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer(mUserPreferences)));
|
||||
mTabPane.getTabs().add(new LabeledTab("P25P2-" + mTabCounterP25P2++, new P25P2Viewer()));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,16 +20,24 @@
|
||||
package io.github.dsheirer.gui.viewer;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||
import io.github.dsheirer.channel.state.SingleChannelState;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.StuffBitsMessage;
|
||||
import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager;
|
||||
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderState;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageFramer;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageProcessor;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.binary.BinaryReader;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.io.File;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -94,9 +102,11 @@ public class P25P1Viewer extends VBox
|
||||
private ProgressIndicator mLoadingIndicator;
|
||||
private MessagePackageViewer mMessagePackageViewer;
|
||||
private StringProperty mLoadedFile = new SimpleStringProperty();
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
public P25P1Viewer()
|
||||
public P25P1Viewer(UserPreferences userPreferences)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
setPadding(new Insets(5));
|
||||
setSpacing(5);
|
||||
|
||||
@@ -155,7 +165,9 @@ public class P25P1Viewer extends VBox
|
||||
List<MessagePackage> messagePackages = new ArrayList<>();
|
||||
P25P1MessageFramer messageFramer = new P25P1MessageFramer(null, 9600);
|
||||
P25P1MessageProcessor messageProcessor = new P25P1MessageProcessor();
|
||||
messageFramer.setListener(messageProcessor);
|
||||
Broadcaster<IMessage> messageBroadcaster = new Broadcaster<>();
|
||||
messageFramer.setListener(messageBroadcaster);
|
||||
messageBroadcaster.addListener(messageProcessor);
|
||||
|
||||
Channel empty = new Channel("Empty");
|
||||
empty.setDecodeConfiguration(new DecodeConfigP25Phase1());
|
||||
@@ -169,10 +181,14 @@ public class P25P1Viewer extends VBox
|
||||
trafficChannelManager.setInterModuleEventBus(eventBus);
|
||||
|
||||
//Register to receive events
|
||||
trafficChannelManager.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent));
|
||||
trafficChannelManager.addDecodeEventListener(messagePackager::add);
|
||||
P25P1DecoderState decoderState = new P25P1DecoderState(empty, trafficChannelManager);
|
||||
decoderState.setDecoderStateListener(decoderStateEvent -> messagePackager.add(decoderStateEvent));
|
||||
decoderState.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent));
|
||||
|
||||
Broadcaster<DecoderStateEvent> decoderStateEventBroadcaster = new Broadcaster<>();
|
||||
decoderState.setDecoderStateListener(decoderStateEventBroadcaster);
|
||||
|
||||
decoderStateEventBroadcaster.addListener(messagePackager::add);
|
||||
decoderState.addDecodeEventListener(messagePackager::add);
|
||||
decoderState.start();
|
||||
|
||||
long frequency = getFrequencyFromFile(mLoadedFile.get());
|
||||
@@ -197,6 +213,16 @@ public class P25P1Viewer extends VBox
|
||||
}
|
||||
});
|
||||
|
||||
P25P1AudioModule audioModule = new P25P1AudioModule(mUserPreferences, new AliasList("debug"));
|
||||
decoderState.setIdentifierUpdateListener(audioModule.getIdentifierUpdateListener());
|
||||
audioModule.setAudioSegmentListener(messagePackager::add);
|
||||
messageBroadcaster.addListener(audioModule);
|
||||
audioModule.start();
|
||||
SingleChannelState singleChannelState = new SingleChannelState(empty, new AliasModel());
|
||||
singleChannelState.setSquelchStateListener(squelchStateEvent -> audioModule.getSquelchStateListener().receive(squelchStateEvent));
|
||||
decoderStateEventBroadcaster.addListener(singleChannelState.getDecoderStateListener());
|
||||
singleChannelState.start();
|
||||
|
||||
try(BinaryReader reader = new BinaryReader(file.toPath(), 200))
|
||||
{
|
||||
while(reader.hasNext())
|
||||
@@ -210,6 +236,10 @@ public class P25P1Viewer extends VBox
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
|
||||
audioModule.stop();
|
||||
decoderState.stop();
|
||||
singleChannelState.stop();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
getLoadingIndicator().setVisible(false);
|
||||
getSelectedFileLabel().setText(file.getName());
|
||||
@@ -444,8 +474,13 @@ public class P25P1Viewer extends VBox
|
||||
channelStartCountColumn.setText("Starts");
|
||||
channelStartCountColumn.setCellValueFactory(new PropertyValueFactory<>("channelStartProcessingRequestCount"));
|
||||
|
||||
TableColumn audioSegmentCountColumn = new TableColumn();
|
||||
audioSegmentCountColumn.setPrefWidth(50);
|
||||
audioSegmentCountColumn.setText("Audio");
|
||||
audioSegmentCountColumn.setCellValueFactory(new PropertyValueFactory<>("audioSegmentCount"));
|
||||
|
||||
mMessagePackageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn,
|
||||
decodeEventCountColumn, decoderStateEventCountColumn, channelStartCountColumn);
|
||||
decodeEventCountColumn, decoderStateEventCountColumn, channelStartCountColumn, audioSegmentCountColumn);
|
||||
}
|
||||
|
||||
return mMessagePackageTableView;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 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
|
||||
@@ -30,6 +30,8 @@ import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU2Message;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDUMessage;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class P25P1AudioModule extends ImbeAudioModule
|
||||
{
|
||||
@@ -38,7 +40,7 @@ public class P25P1AudioModule extends ImbeAudioModule
|
||||
|
||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
|
||||
private LDU1Message mCachedLDU1Message = null;
|
||||
private List<LDUMessage> mCachedLDUMessages = new ArrayList<>();
|
||||
|
||||
public P25P1AudioModule(UserPreferences userPreferences, AliasList aliasList)
|
||||
{
|
||||
@@ -81,37 +83,46 @@ public class P25P1AudioModule extends ImbeAudioModule
|
||||
{
|
||||
if(mEncryptedCallStateEstablished)
|
||||
{
|
||||
if(message instanceof LDUMessage)
|
||||
if(message instanceof LDUMessage ldu)
|
||||
{
|
||||
processAudio((LDUMessage)message);
|
||||
processAudio(ldu);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(message instanceof HDUMessage)
|
||||
if(message instanceof HDUMessage hdu && hdu.isValid())
|
||||
{
|
||||
mEncryptedCallStateEstablished = true;
|
||||
mEncryptedCall = ((HDUMessage)message).getHeaderData().isEncryptedAudio();
|
||||
mEncryptedCall = hdu.getHeaderData().isEncryptedAudio();
|
||||
}
|
||||
else if(message instanceof LDU1Message)
|
||||
else if(message instanceof LDU1Message ldu1)
|
||||
{
|
||||
//When we receive an LDU1 message without first receiving the HDU message, cache the LDU1 Message
|
||||
//until we can determine the encrypted call state from the next LDU2 message
|
||||
mCachedLDU1Message = (LDU1Message)message;
|
||||
mCachedLDUMessages.add(ldu1);
|
||||
}
|
||||
else if(message instanceof LDU2Message)
|
||||
else if(message instanceof LDU2Message ldu2)
|
||||
{
|
||||
mEncryptedCallStateEstablished = true;
|
||||
LDU2Message ldu2 = (LDU2Message)message;
|
||||
mEncryptedCall = ldu2.getEncryptionSyncParameters().isEncryptedAudio();
|
||||
|
||||
if(mCachedLDU1Message != null)
|
||||
if(ldu2.getEncryptionSyncParameters().isValid())
|
||||
{
|
||||
processAudio(mCachedLDU1Message);
|
||||
mCachedLDU1Message = null;
|
||||
mEncryptedCallStateEstablished = true;
|
||||
mEncryptedCall = ldu2.getEncryptionSyncParameters().isEncryptedAudio();
|
||||
}
|
||||
|
||||
processAudio(ldu2);
|
||||
if(mEncryptedCallStateEstablished)
|
||||
{
|
||||
for(LDUMessage cachedLdu : mCachedLDUMessages)
|
||||
{
|
||||
processAudio(cachedLdu);
|
||||
}
|
||||
|
||||
mCachedLDUMessages.clear();
|
||||
processAudio(ldu2);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCachedLDUMessages.add(ldu2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,7 +163,7 @@ public class P25P1AudioModule extends ImbeAudioModule
|
||||
closeAudioSegment();
|
||||
mEncryptedCallStateEstablished = false;
|
||||
mEncryptedCall = false;
|
||||
mCachedLDU1Message = null;
|
||||
mCachedLDUMessages.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlOpcode;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisReturnToControlChannel;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaEmergencyAlarmActivation;
|
||||
@@ -891,15 +890,9 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
||||
|
||||
if(lcw != null && lcw.isValid())
|
||||
{
|
||||
//Send an ACTIVE decoder state event for everything except the CALL TERMINATION opcode which is
|
||||
//handled by the processLC() method.
|
||||
if(lcw.getOpcode() != LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION)
|
||||
{
|
||||
//Set the state to ACTIVE while the call continues in hangtime. The processLC() method will signal
|
||||
// the channel teardown.
|
||||
broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE));
|
||||
}
|
||||
|
||||
//Set the state to ACTIVE while the call continues in hangtime. The processLC() method will signal
|
||||
// the channel teardown when that happens.
|
||||
broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE));
|
||||
processLC(lcw, message.getTimestamp(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,11 @@ public class HDUMessage extends P25P1Message
|
||||
|
||||
if(irrecoverableErrors)
|
||||
{
|
||||
//Set the header data as invalid
|
||||
mHeaderData.setValid(false);
|
||||
|
||||
//Est the whole HDU message as invalid.
|
||||
setValid(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user