From abaa35a4424a7b0e4de0bc87c101e273326bc58a Mon Sep 17 00:00:00 2001 From: Dennis Sheirer Date: Sun, 29 Sep 2024 07:42:25 -0400 Subject: [PATCH] #1994 P25 Phase1 missing subsequent calls audio when multiple conversations on the same traffic channel. 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. --- .../dsheirer/gui/viewer/MessagePackage.java | 34 +++++++++++++- .../gui/viewer/MessagePackageViewer.java | 32 ++++++++++++- .../dsheirer/gui/viewer/MessagePackager.java | 14 +++++- .../gui/viewer/MessageRecordingViewer.java | 6 ++- .../dsheirer/gui/viewer/P25P1Viewer.java | 47 ++++++++++++++++--- .../decode/p25/audio/P25P1AudioModule.java | 47 ++++++++++++------- .../decode/p25/phase1/P25P1DecoderState.java | 13 ++--- .../p25/phase1/message/hdu/HDUMessage.java | 4 ++ 8 files changed, 157 insertions(+), 40 deletions(-) diff --git a/src/main/java/io/github/dsheirer/gui/viewer/MessagePackage.java b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackage.java index a912f88c8..ec49879b9 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/MessagePackage.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackage.java @@ -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 mDecoderStateEvents = new ArrayList<>(); private List mDecodeEvents = new ArrayList<>(); private ChannelStartProcessingRequest mChannelStartProcessingRequest; + private AudioSegment mAudioSegment; /** * Constructs an instance @@ -152,8 +153,39 @@ public int getChannelStartProcessingRequestCount() 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; + } } diff --git a/src/main/java/io/github/dsheirer/gui/viewer/MessagePackageViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackageViewer.java index 061c71c05..a28cfda58 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/MessagePackageViewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackageViewer.java @@ -22,6 +22,8 @@ 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 mDecoderStateEventTableView; private TableView mDecodeEventTableView; private IdentifierCollectionViewer mIdentifierCollectionViewer; + private IdentifierCollectionViewer mAudioSegmentIdCollectionViewer; private ChannelStartProcessingRequestViewer mChannelStartProcessingRequestViewer; /** @@ -63,8 +66,14 @@ public MessagePackageViewer() 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 void set(MessagePackage messagePackage) { getDecodeEventTableView().getSelectionModel().select(0); } + + if(messagePackage.getAudioSegment() != null) + { + getAudioSegmentIdCollectionViewer().set(messagePackage.getAudioSegment().getIdentifierCollection()); + } + else + { + getAudioSegmentIdCollectionViewer().set(null); + } } } @@ -109,6 +127,16 @@ private IdentifierCollectionViewer getIdentifierCollectionViewer() return mIdentifierCollectionViewer; } + private IdentifierCollectionViewer getAudioSegmentIdCollectionViewer() + { + if(mAudioSegmentIdCollectionViewer == null) + { + mAudioSegmentIdCollectionViewer = new IdentifierCollectionViewer(); + } + + return mAudioSegmentIdCollectionViewer; + } + private Label getMessageLabel() { if(mMessageLabel == null) diff --git a/src/main/java/io/github/dsheirer/gui/viewer/MessagePackager.java b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackager.java index ef5fb0744..b4c131d1b 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/MessagePackager.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/MessagePackager.java @@ -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 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. diff --git a/src/main/java/io/github/dsheirer/gui/viewer/MessageRecordingViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/MessageRecordingViewer.java index 6729fb1a4..c7ff473ef 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/MessageRecordingViewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/MessageRecordingViewer.java @@ -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 MenuBar getMenuBar() }); 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 TabPane getTabPane() 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())); } diff --git a/src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java b/src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java index d50c9d555..7937e54e9 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java @@ -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 @@ private void load(File file) List messagePackages = new ArrayList<>(); P25P1MessageFramer messageFramer = new P25P1MessageFramer(null, 9600); P25P1MessageProcessor messageProcessor = new P25P1MessageProcessor(); - messageFramer.setListener(messageProcessor); + Broadcaster messageBroadcaster = new Broadcaster<>(); + messageFramer.setListener(messageBroadcaster); + messageBroadcaster.addListener(messageProcessor); Channel empty = new Channel("Empty"); empty.setDecodeConfiguration(new DecodeConfigP25Phase1()); @@ -169,10 +181,14 @@ private void load(File file) 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 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 @@ private void load(File file) } }); + 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 @@ private void load(File file) ioe.printStackTrace(); } + audioModule.stop(); + decoderState.stop(); + singleChannelState.stop(); + Platform.runLater(() -> { getLoadingIndicator().setVisible(false); getSelectedFileLabel().setText(file.getName()); @@ -444,8 +474,13 @@ private TableView getMessagePackageTableView() 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; diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1AudioModule.java b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1AudioModule.java index 41ab474cc..d15be3684 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1AudioModule.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/audio/P25P1AudioModule.java @@ -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.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 mCachedLDUMessages = new ArrayList<>(); public P25P1AudioModule(UserPreferences userPreferences, AliasList aliasList) { @@ -81,37 +83,46 @@ public void receive(IMessage message) { 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 void receive(SquelchStateEvent event) closeAudioSegment(); mEncryptedCallStateEstablished = false; mEncryptedCall = false; - mCachedLDU1Message = null; + mCachedLDUMessages.clear(); } } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java index 9c9d2d229..c20c61202 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java @@ -60,7 +60,6 @@ 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 @@ private void processTDULC(P25P1Message message) 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); } } diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HDUMessage.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HDUMessage.java index ccce03547..f0f477a9d 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HDUMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/hdu/HDUMessage.java @@ -184,7 +184,11 @@ private void extractHeaderData() if(irrecoverableErrors) { + //Set the header data as invalid mHeaderData.setValid(false); + + //Est the whole HDU message as invalid. + setValid(false); } else {