#1045 P25 encrypted call ignore setting. Cleanup several other errors. (#2636)

Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
Denny Sheirer
2026-11-13 05:14:45 -05:00
committed by GitHub
parent aad971db9c
commit 73fc85aa76
7 changed files with 494 additions and 4 deletions

View File

@@ -73,6 +73,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
private ToggleSwitch mIgnoreDataCallsButton;
private ToggleSwitch mIgnoreEncryptedCallsButton;
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
private SegmentedButton mModulationSegmentedButton;
private ToggleButton mC4FMToggleButton;
@@ -157,6 +158,15 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
GridPane.setConstraints(directionLabel, 5, row);
gridPane.getChildren().add(directionLabel);
GridPane.setConstraints(getIgnoreEncryptedCallsButton(), 6, row);
gridPane.getChildren().add(getIgnoreEncryptedCallsButton());
Label ignoreEncryptedLabel = new Label("Ignore Encrypted Calls");
ignoreEncryptedLabel.setLabelFor(getIgnoreEncryptedCallsButton());
GridPane.setHalignment(ignoreEncryptedLabel, HPos.LEFT);
GridPane.setConstraints(ignoreEncryptedLabel, 7, row);
gridPane.getChildren().add(ignoreEncryptedLabel);
row++;
Label modulationHelpLabel = new Label("C4FM: repeaters and non-simulcast trunked systems. LSM: simulcast trunked systems.");
@@ -343,6 +353,21 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
return mLSMToggleButton;
}
private ToggleSwitch getIgnoreEncryptedCallsButton()
{
if(mIgnoreEncryptedCallsButton == null)
{
mIgnoreEncryptedCallsButton = new AccessibleToggleSwitch();
mIgnoreEncryptedCallsButton.setDisable(true);
mIgnoreEncryptedCallsButton.setTooltip(new Tooltip("Don't tune a traffic channel when the channel grant indicates the call is encrypted. " +
"Phase 2: the channel is still tuned when the other timeslot has a clear call."));
mIgnoreEncryptedCallsButton.selectedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mIgnoreEncryptedCallsButton;
}
private ToggleSwitch getIgnoreDataCallsButton()
{
if(mIgnoreDataCallsButton == null)
@@ -398,11 +423,13 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
protected void setDecoderConfiguration(DecodeConfiguration config)
{
getIgnoreDataCallsButton().setDisable(config == null);
getIgnoreEncryptedCallsButton().setDisable(config == null);
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
if(config instanceof DecodeConfigP25Phase1 decodeConfig)
{
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
getIgnoreEncryptedCallsButton().setSelected(decodeConfig.getIgnoreEncryptedCalls());
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
if(decodeConfig.getModulation() == Modulation.C4FM)
{
@@ -427,6 +454,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
else
{
getIgnoreDataCallsButton().setSelected(false);
getIgnoreEncryptedCallsButton().setSelected(false);
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
getJmbeAGCButton().setDisable(true);
getJmbeNoiseGainSlider().setDisable(true);
@@ -454,6 +482,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
}
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
config.setIgnoreEncryptedCalls(getIgnoreEncryptedCallsButton().isSelected());
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
config.setModulation(getC4FMToggleButton().isSelected() ? Modulation.C4FM : Modulation.CQPSK);
config.setAGC(getJmbeAGCButton().selectedProperty().getValue());

View File

@@ -71,6 +71,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
private IntegerTextField mSystemTextField;
private IntegerTextField mNacTextField;
private ToggleSwitch mIgnoreDataCallsButton;
private ToggleSwitch mIgnoreEncryptedCallsButton;
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
private ToggleSwitch mJmbeAGCButton;
private Slider mJmbeToneGainSlider;
@@ -142,6 +143,15 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
GridPane.setConstraints(directionLabel, 3, row);
gridPane.getChildren().add(directionLabel);
GridPane.setConstraints(getIgnoreEncryptedCallsButton(), 4, row);
gridPane.getChildren().add(getIgnoreEncryptedCallsButton());
Label ignoreEncryptedLabel = new Label("Ignore Encrypted Calls");
ignoreEncryptedLabel.setLabelFor(getIgnoreEncryptedCallsButton());
GridPane.setHalignment(ignoreEncryptedLabel, HPos.LEFT);
GridPane.setConstraints(ignoreEncryptedLabel, 5, row);
gridPane.getChildren().add(ignoreEncryptedLabel);
Label wacnLabel = new Label("WACN");
wacnLabel.setLabelFor(getWacnTextField());
GridPane.setHalignment(wacnLabel, HPos.RIGHT);
@@ -300,6 +310,21 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
return mEventLogConfigurationEditor;
}
private ToggleSwitch getIgnoreEncryptedCallsButton()
{
if(mIgnoreEncryptedCallsButton == null)
{
mIgnoreEncryptedCallsButton = new AccessibleToggleSwitch();
mIgnoreEncryptedCallsButton.setDisable(true);
mIgnoreEncryptedCallsButton.setTooltip(new Tooltip("Don't tune a traffic channel when the channel grant indicates the call is encrypted. " +
"Phase 2: the channel is still tuned when the other timeslot has a clear call."));
mIgnoreEncryptedCallsButton.selectedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mIgnoreEncryptedCallsButton;
}
private ToggleSwitch getIgnoreDataCallsButton()
{
if(mIgnoreDataCallsButton == null)
@@ -417,6 +442,8 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
getIgnoreDataCallsButton().setDisable(false);
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
getIgnoreEncryptedCallsButton().setDisable(false);
getIgnoreEncryptedCallsButton().setSelected(decodeConfig.getIgnoreEncryptedCalls());
getTrafficChannelPoolSizeSpinner().setDisable(false);
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
getJmbeAGCButton().setSelected(decodeConfig.isAGC());
@@ -433,6 +460,8 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
getSystemTextField().setDisable(true);
getNacTextField().setDisable(true);
getIgnoreDataCallsButton().setDisable(true);
getIgnoreEncryptedCallsButton().setDisable(true);
getIgnoreEncryptedCallsButton().setSelected(false);
getTrafficChannelPoolSizeSpinner().setDisable(true);
getJmbeAGCButton().setDisable(true);
getJmbeNoiseGainSlider().setDisable(true);
@@ -465,6 +494,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
int nac = getNacTextField().get();
config.setScrambleParameters(new ScrambleParameters(wacn, system, nac));
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
config.setIgnoreEncryptedCalls(getIgnoreEncryptedCallsButton().isSelected());
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
config.setAGC(getJmbeAGCButton().selectedProperty().getValue());
config.setComfortNoiseGain((float)getJmbeNoiseGainSlider().getValue());

View File

@@ -922,6 +922,7 @@ public class DecoderFactory
DecodeConfigP25Phase1 originalP25P1 = (DecodeConfigP25Phase1)config;
DecodeConfigP25Phase1 copyP25P1 = new DecodeConfigP25Phase1();
copyP25P1.setIgnoreDataCalls(originalP25P1.getIgnoreDataCalls());
copyP25P1.setIgnoreEncryptedCalls(originalP25P1.getIgnoreEncryptedCalls());
copyP25P1.setModulation(originalP25P1.getModulation());
copyP25P1.setTrafficChannelPoolSize(originalP25P1.getTrafficChannelPoolSize());
transferJMBESettings(originalP25P1, copyP25P1);
@@ -929,6 +930,9 @@ public class DecoderFactory
case P25_PHASE2:
DecodeConfigP25Phase2 originalP25P2 = (DecodeConfigP25Phase2)config;
DecodeConfigP25Phase2 copyP25P2 = new DecodeConfigP25Phase2();
copyP25P2.setIgnoreDataCalls(originalP25P2.getIgnoreDataCalls());
copyP25P2.setIgnoreEncryptedCalls(originalP25P2.getIgnoreEncryptedCalls());
copyP25P2.setTrafficChannelPoolSize(originalP25P2.getTrafficChannelPoolSize());
transferJMBESettings(originalP25P2, copyP25P2);
if(originalP25P2.getScrambleParameters() != null)

View File

@@ -28,6 +28,8 @@ import io.github.dsheirer.module.decode.event.ChannelEventTracker;
*/
public class P25ChannelEventTracker extends ChannelEventTracker<P25ChannelGrantEvent>
{
private boolean mEncrypted;
/**
* Constructs an instance
*
@@ -37,4 +39,24 @@ public class P25ChannelEventTracker extends ChannelEventTracker<P25ChannelGrantE
{
super(event);
}
/**
* Indicates if this call is known to be encrypted. Once a call is flagged as encrypted, the flag persists for the
* lifetime of this tracker because many channel grant update messages don't carry service options and those
* updates are processed with empty (ie unencrypted) service options.
* @return true if the call is known to be encrypted.
*/
public boolean isEncrypted()
{
return mEncrypted;
}
/**
* Flags this call as encrypted.
* @param encrypted true if encrypted.
*/
public void setEncrypted(boolean encrypted)
{
mEncrypted = encrypted;
}
}

View File

@@ -124,6 +124,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
private ScrambleParameters mPhase2ScrambleParameters;
private Listener<IMessage> mMessageListener;
private boolean mIgnoreDataCalls;
private boolean mIgnoreEncryptedCalls;
//Used only for data calls
private final DecodeEventDuplicateDetector mDuplicateDetector = new DecodeEventDuplicateDetector();
private final TalkerAliasManager mTalkerAliasManager = new TalkerAliasManager();
@@ -139,6 +140,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase1 phase1)
{
mIgnoreDataCalls = phase1.getIgnoreDataCalls();
mIgnoreEncryptedCalls = phase1.getIgnoreEncryptedCalls();
createPhase1TrafficChannels(phase1.getTrafficChannelPoolSize(), phase1);
DecodeConfigP25Phase2 phase2 = new DecodeConfigP25Phase2();
DecoderFactory.transferJMBESettings(phase1, phase2);
@@ -147,6 +149,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
else if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase2 phase2)
{
mIgnoreDataCalls = phase2.getIgnoreDataCalls();
mIgnoreEncryptedCalls = phase2.getIgnoreEncryptedCalls();
DecodeConfigP25Phase1 phase1 = new DecodeConfigP25Phase1();
DecoderFactory.transferJMBESettings(phase2, phase1);
createPhase1TrafficChannels(phase2.getTrafficChannelPoolSize(), phase1);
@@ -619,7 +622,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
//update the ending timestamp so that the duration value is correctly calculated
trackerTS2.updateDurationTraffic(timestamp);
broadcast(trackerTS1);
broadcast(trackerTS2);
}
finally
{
@@ -1149,6 +1152,30 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
return completed;
}
/**
* Indicates if the service options identify the call as encrypted.
* @param serviceOptions to check, optional null
* @return true if encrypted
*/
private static boolean isEncrypted(ServiceOptions serviceOptions)
{
return serviceOptions != null && serviceOptions.isEncrypted();
}
/**
* Updates the tracker's encrypted flag from the current service options. When the talker changed (ie a new
* tracker was created from a channel grant that carries service options) the tracker takes the current value.
* Otherwise, the encrypted flag is only ever set (never cleared) since many update messages carry empty service
* options, which would otherwise make a known-encrypted call look unencrypted.
*/
private static void updateEncrypted(P25ChannelEventTracker tracker, ServiceOptions serviceOptions)
{
if(isEncrypted(serviceOptions))
{
tracker.setEncrypted(true);
}
}
/**
* Sends a channel start request to the ChannelProcessingManager.
*
@@ -1238,6 +1265,8 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
addTracker(tracker, frequency, P25P1Message.TIMESLOT_1);
}
updateEncrypted(tracker, serviceOptions);
//The tracked event can have an empty FROM identifier at start of call ... update here
tracker.addIdentifierIfMissing(from);
@@ -1248,7 +1277,8 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
//Even though we have a tracked event, the initial channel grant may have been rejected. Check to see if there
//is a traffic channel allocated. If not, allocate one and update the event description.
if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !(mIgnoreDataCalls && isDataChannelGrant))
if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !(mIgnoreDataCalls && isDataChannelGrant) &&
!(mIgnoreEncryptedCalls && tracker.isEncrypted()))
{
Channel trafficChannel = mAvailablePhase1TrafficChannelQueue.poll();
@@ -1293,6 +1323,22 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
return;
}
//Don't allocate a traffic channel for an encrypted call, unless a traffic channel is already allocated for the
//frequency in which case the call will be processed by the running traffic channel.
if(mIgnoreEncryptedCalls && isEncrypted(serviceOptions) && !mAllocatedTrafficChannelMap.containsKey(frequency))
{
P25ChannelGrantEvent event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions)
.channelDescriptor(apco25Channel)
.details("IGNORED: ENCRYPTED - PHASE 1 CHANNEL GRANT " + serviceOptions)
.identifiers(ic)
.build();
tracker = new P25ChannelEventTracker(event);
tracker.setEncrypted(true);
addTracker(tracker, frequency, P25P1Message.TIMESLOT_1);
broadcast(tracker);
return;
}
String details = isDataChannelGrant ? "PHASE 1 DATA CHANNEL GRANT " : "PHASE 1 CHANNEL GRANT " +
(serviceOptions != null ? serviceOptions : "");
@@ -1302,6 +1348,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
.identifiers(ic)
.build();
tracker = new P25ChannelEventTracker(event);
updateEncrypted(tracker, serviceOptions);
addTracker(tracker, frequency, P25P1Message.TIMESLOT_1);
//Allocate a traffic channel for the downlink frequency if one isn't already allocated
@@ -1367,14 +1414,19 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
broadcast(tracker);
}
updateEncrypted(tracker, serviceOptions);
//update the ending timestamp so that the duration value is correctly calculated
tracker.updateDurationControl(timestamp);
broadcast(tracker);
//Even though we have a tracked event, the initial channel grant may have been rejected. Check to see if there
//is a traffic channel allocated. If not, allocate one and update the event description.
//Note: a phase 2 traffic channel carries two timeslots. An encrypted call on this timeslot doesn't cause
//the channel to be allocated, but a clear call on the other timeslot will allocate the channel through
//its own tracker and then both timeslots are processed by the traffic channel.
if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !(mIgnoreDataCalls && isDataChannelGrant) &&
(getCurrentControlFrequency() != frequency))
!(mIgnoreEncryptedCalls && tracker.isEncrypted()) && (getCurrentControlFrequency() != frequency))
{
Channel trafficChannel = mAvailablePhase2TrafficChannelQueue.poll();
@@ -1404,7 +1456,25 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
.build();
tracker = new P25ChannelEventTracker(event);
addTracker(tracker, frequency, P25P1Message.TIMESLOT_1);
addTracker(tracker, frequency, timeslot);
broadcast(tracker);
return;
}
//Don't allocate a traffic channel for an encrypted call. If the frequency is already allocated (ie there's a
//call on the other timeslot), the running traffic channel will process this timeslot as well.
if(mIgnoreEncryptedCalls && isEncrypted(serviceOptions) && !mAllocatedTrafficChannelMap.containsKey(frequency))
{
P25ChannelGrantEvent event = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions)
.channelDescriptor(apco25Channel)
.details("IGNORED: ENCRYPTED - PHASE 2 CHANNEL GRANT " + serviceOptions)
.identifiers(ic)
.timeslot(timeslot)
.build();
tracker = new P25ChannelEventTracker(event);
tracker.setEncrypted(true);
addTracker(tracker, frequency, timeslot);
broadcast(tracker);
return;
}
@@ -1417,6 +1487,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
.build();
tracker = new P25ChannelEventTracker(event);
updateEncrypted(tracker, serviceOptions);
addTracker(tracker, frequency, timeslot);
//Allocate a traffic channel for the downlink frequency if one isn't already allocated

View File

@@ -34,6 +34,7 @@ public abstract class DecodeConfigP25 extends DecodeConfigJMBE
{
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
private boolean mIgnoreDataCalls = false;
private boolean mIgnoreEncryptedCalls = false;
public DecodeConfigP25()
{
@@ -50,6 +51,25 @@ public abstract class DecodeConfigP25 extends DecodeConfigJMBE
mIgnoreDataCalls = ignore;
}
/**
* Indicates if the traffic channel manager should not allocate (ie tune) a traffic channel for a channel grant
* where the service options indicate the call is encrypted.
*/
@JacksonXmlProperty(isAttribute = true, localName = "ignore_encrypted_calls")
public boolean getIgnoreEncryptedCalls()
{
return mIgnoreEncryptedCalls;
}
/**
* Sets the ignore encrypted calls option.
* @param ignore true to not allocate traffic channels for encrypted calls.
*/
public void setIgnoreEncryptedCalls(boolean ignore)
{
mIgnoreEncryptedCalls = ignore;
}
@JacksonXmlProperty(isAttribute = true, localName = "traffic_channel_pool_size")
public int getTrafficChannelPoolSize()