mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 17:47:32 -05:00
Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
@@ -73,6 +73,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
private EventLogConfigurationEditor mEventLogConfigurationEditor;
|
private EventLogConfigurationEditor mEventLogConfigurationEditor;
|
||||||
private RecordConfigurationEditor mRecordConfigurationEditor;
|
private RecordConfigurationEditor mRecordConfigurationEditor;
|
||||||
private ToggleSwitch mIgnoreDataCallsButton;
|
private ToggleSwitch mIgnoreDataCallsButton;
|
||||||
|
private ToggleSwitch mIgnoreEncryptedCallsButton;
|
||||||
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
||||||
private SegmentedButton mModulationSegmentedButton;
|
private SegmentedButton mModulationSegmentedButton;
|
||||||
private ToggleButton mC4FMToggleButton;
|
private ToggleButton mC4FMToggleButton;
|
||||||
@@ -157,6 +158,15 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
GridPane.setConstraints(directionLabel, 5, row);
|
GridPane.setConstraints(directionLabel, 5, row);
|
||||||
gridPane.getChildren().add(directionLabel);
|
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++;
|
row++;
|
||||||
|
|
||||||
Label modulationHelpLabel = new Label("C4FM: repeaters and non-simulcast trunked systems. LSM: simulcast trunked systems.");
|
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;
|
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()
|
private ToggleSwitch getIgnoreDataCallsButton()
|
||||||
{
|
{
|
||||||
if(mIgnoreDataCallsButton == null)
|
if(mIgnoreDataCallsButton == null)
|
||||||
@@ -398,11 +423,13 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
protected void setDecoderConfiguration(DecodeConfiguration config)
|
protected void setDecoderConfiguration(DecodeConfiguration config)
|
||||||
{
|
{
|
||||||
getIgnoreDataCallsButton().setDisable(config == null);
|
getIgnoreDataCallsButton().setDisable(config == null);
|
||||||
|
getIgnoreEncryptedCallsButton().setDisable(config == null);
|
||||||
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
|
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
|
||||||
|
|
||||||
if(config instanceof DecodeConfigP25Phase1 decodeConfig)
|
if(config instanceof DecodeConfigP25Phase1 decodeConfig)
|
||||||
{
|
{
|
||||||
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
||||||
|
getIgnoreEncryptedCallsButton().setSelected(decodeConfig.getIgnoreEncryptedCalls());
|
||||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
||||||
if(decodeConfig.getModulation() == Modulation.C4FM)
|
if(decodeConfig.getModulation() == Modulation.C4FM)
|
||||||
{
|
{
|
||||||
@@ -427,6 +454,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
getIgnoreDataCallsButton().setSelected(false);
|
getIgnoreDataCallsButton().setSelected(false);
|
||||||
|
getIgnoreEncryptedCallsButton().setSelected(false);
|
||||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
|
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
|
||||||
getJmbeAGCButton().setDisable(true);
|
getJmbeAGCButton().setDisable(true);
|
||||||
getJmbeNoiseGainSlider().setDisable(true);
|
getJmbeNoiseGainSlider().setDisable(true);
|
||||||
@@ -454,6 +482,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
||||||
|
config.setIgnoreEncryptedCalls(getIgnoreEncryptedCallsButton().isSelected());
|
||||||
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
||||||
config.setModulation(getC4FMToggleButton().isSelected() ? Modulation.C4FM : Modulation.CQPSK);
|
config.setModulation(getC4FMToggleButton().isSelected() ? Modulation.C4FM : Modulation.CQPSK);
|
||||||
config.setAGC(getJmbeAGCButton().selectedProperty().getValue());
|
config.setAGC(getJmbeAGCButton().selectedProperty().getValue());
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
private IntegerTextField mSystemTextField;
|
private IntegerTextField mSystemTextField;
|
||||||
private IntegerTextField mNacTextField;
|
private IntegerTextField mNacTextField;
|
||||||
private ToggleSwitch mIgnoreDataCallsButton;
|
private ToggleSwitch mIgnoreDataCallsButton;
|
||||||
|
private ToggleSwitch mIgnoreEncryptedCallsButton;
|
||||||
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
||||||
private ToggleSwitch mJmbeAGCButton;
|
private ToggleSwitch mJmbeAGCButton;
|
||||||
private Slider mJmbeToneGainSlider;
|
private Slider mJmbeToneGainSlider;
|
||||||
@@ -142,6 +143,15 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
GridPane.setConstraints(directionLabel, 3, row);
|
GridPane.setConstraints(directionLabel, 3, row);
|
||||||
gridPane.getChildren().add(directionLabel);
|
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");
|
Label wacnLabel = new Label("WACN");
|
||||||
wacnLabel.setLabelFor(getWacnTextField());
|
wacnLabel.setLabelFor(getWacnTextField());
|
||||||
GridPane.setHalignment(wacnLabel, HPos.RIGHT);
|
GridPane.setHalignment(wacnLabel, HPos.RIGHT);
|
||||||
@@ -300,6 +310,21 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
return mEventLogConfigurationEditor;
|
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()
|
private ToggleSwitch getIgnoreDataCallsButton()
|
||||||
{
|
{
|
||||||
if(mIgnoreDataCallsButton == null)
|
if(mIgnoreDataCallsButton == null)
|
||||||
@@ -417,6 +442,8 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
|
|
||||||
getIgnoreDataCallsButton().setDisable(false);
|
getIgnoreDataCallsButton().setDisable(false);
|
||||||
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
||||||
|
getIgnoreEncryptedCallsButton().setDisable(false);
|
||||||
|
getIgnoreEncryptedCallsButton().setSelected(decodeConfig.getIgnoreEncryptedCalls());
|
||||||
getTrafficChannelPoolSizeSpinner().setDisable(false);
|
getTrafficChannelPoolSizeSpinner().setDisable(false);
|
||||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
||||||
getJmbeAGCButton().setSelected(decodeConfig.isAGC());
|
getJmbeAGCButton().setSelected(decodeConfig.isAGC());
|
||||||
@@ -433,6 +460,8 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
getSystemTextField().setDisable(true);
|
getSystemTextField().setDisable(true);
|
||||||
getNacTextField().setDisable(true);
|
getNacTextField().setDisable(true);
|
||||||
getIgnoreDataCallsButton().setDisable(true);
|
getIgnoreDataCallsButton().setDisable(true);
|
||||||
|
getIgnoreEncryptedCallsButton().setDisable(true);
|
||||||
|
getIgnoreEncryptedCallsButton().setSelected(false);
|
||||||
getTrafficChannelPoolSizeSpinner().setDisable(true);
|
getTrafficChannelPoolSizeSpinner().setDisable(true);
|
||||||
getJmbeAGCButton().setDisable(true);
|
getJmbeAGCButton().setDisable(true);
|
||||||
getJmbeNoiseGainSlider().setDisable(true);
|
getJmbeNoiseGainSlider().setDisable(true);
|
||||||
@@ -465,6 +494,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
int nac = getNacTextField().get();
|
int nac = getNacTextField().get();
|
||||||
config.setScrambleParameters(new ScrambleParameters(wacn, system, nac));
|
config.setScrambleParameters(new ScrambleParameters(wacn, system, nac));
|
||||||
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
||||||
|
config.setIgnoreEncryptedCalls(getIgnoreEncryptedCallsButton().isSelected());
|
||||||
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
||||||
config.setAGC(getJmbeAGCButton().selectedProperty().getValue());
|
config.setAGC(getJmbeAGCButton().selectedProperty().getValue());
|
||||||
config.setComfortNoiseGain((float)getJmbeNoiseGainSlider().getValue());
|
config.setComfortNoiseGain((float)getJmbeNoiseGainSlider().getValue());
|
||||||
|
|||||||
@@ -922,6 +922,7 @@ public class DecoderFactory
|
|||||||
DecodeConfigP25Phase1 originalP25P1 = (DecodeConfigP25Phase1)config;
|
DecodeConfigP25Phase1 originalP25P1 = (DecodeConfigP25Phase1)config;
|
||||||
DecodeConfigP25Phase1 copyP25P1 = new DecodeConfigP25Phase1();
|
DecodeConfigP25Phase1 copyP25P1 = new DecodeConfigP25Phase1();
|
||||||
copyP25P1.setIgnoreDataCalls(originalP25P1.getIgnoreDataCalls());
|
copyP25P1.setIgnoreDataCalls(originalP25P1.getIgnoreDataCalls());
|
||||||
|
copyP25P1.setIgnoreEncryptedCalls(originalP25P1.getIgnoreEncryptedCalls());
|
||||||
copyP25P1.setModulation(originalP25P1.getModulation());
|
copyP25P1.setModulation(originalP25P1.getModulation());
|
||||||
copyP25P1.setTrafficChannelPoolSize(originalP25P1.getTrafficChannelPoolSize());
|
copyP25P1.setTrafficChannelPoolSize(originalP25P1.getTrafficChannelPoolSize());
|
||||||
transferJMBESettings(originalP25P1, copyP25P1);
|
transferJMBESettings(originalP25P1, copyP25P1);
|
||||||
@@ -929,6 +930,9 @@ public class DecoderFactory
|
|||||||
case P25_PHASE2:
|
case P25_PHASE2:
|
||||||
DecodeConfigP25Phase2 originalP25P2 = (DecodeConfigP25Phase2)config;
|
DecodeConfigP25Phase2 originalP25P2 = (DecodeConfigP25Phase2)config;
|
||||||
DecodeConfigP25Phase2 copyP25P2 = new DecodeConfigP25Phase2();
|
DecodeConfigP25Phase2 copyP25P2 = new DecodeConfigP25Phase2();
|
||||||
|
copyP25P2.setIgnoreDataCalls(originalP25P2.getIgnoreDataCalls());
|
||||||
|
copyP25P2.setIgnoreEncryptedCalls(originalP25P2.getIgnoreEncryptedCalls());
|
||||||
|
copyP25P2.setTrafficChannelPoolSize(originalP25P2.getTrafficChannelPoolSize());
|
||||||
transferJMBESettings(originalP25P2, copyP25P2);
|
transferJMBESettings(originalP25P2, copyP25P2);
|
||||||
|
|
||||||
if(originalP25P2.getScrambleParameters() != null)
|
if(originalP25P2.getScrambleParameters() != null)
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import io.github.dsheirer.module.decode.event.ChannelEventTracker;
|
|||||||
*/
|
*/
|
||||||
public class P25ChannelEventTracker extends ChannelEventTracker<P25ChannelGrantEvent>
|
public class P25ChannelEventTracker extends ChannelEventTracker<P25ChannelGrantEvent>
|
||||||
{
|
{
|
||||||
|
private boolean mEncrypted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
*
|
*
|
||||||
@@ -37,4 +39,24 @@ public class P25ChannelEventTracker extends ChannelEventTracker<P25ChannelGrantE
|
|||||||
{
|
{
|
||||||
super(event);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
private ScrambleParameters mPhase2ScrambleParameters;
|
private ScrambleParameters mPhase2ScrambleParameters;
|
||||||
private Listener<IMessage> mMessageListener;
|
private Listener<IMessage> mMessageListener;
|
||||||
private boolean mIgnoreDataCalls;
|
private boolean mIgnoreDataCalls;
|
||||||
|
private boolean mIgnoreEncryptedCalls;
|
||||||
//Used only for data calls
|
//Used only for data calls
|
||||||
private final DecodeEventDuplicateDetector mDuplicateDetector = new DecodeEventDuplicateDetector();
|
private final DecodeEventDuplicateDetector mDuplicateDetector = new DecodeEventDuplicateDetector();
|
||||||
private final TalkerAliasManager mTalkerAliasManager = new TalkerAliasManager();
|
private final TalkerAliasManager mTalkerAliasManager = new TalkerAliasManager();
|
||||||
@@ -139,6 +140,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase1 phase1)
|
if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase1 phase1)
|
||||||
{
|
{
|
||||||
mIgnoreDataCalls = phase1.getIgnoreDataCalls();
|
mIgnoreDataCalls = phase1.getIgnoreDataCalls();
|
||||||
|
mIgnoreEncryptedCalls = phase1.getIgnoreEncryptedCalls();
|
||||||
createPhase1TrafficChannels(phase1.getTrafficChannelPoolSize(), phase1);
|
createPhase1TrafficChannels(phase1.getTrafficChannelPoolSize(), phase1);
|
||||||
DecodeConfigP25Phase2 phase2 = new DecodeConfigP25Phase2();
|
DecodeConfigP25Phase2 phase2 = new DecodeConfigP25Phase2();
|
||||||
DecoderFactory.transferJMBESettings(phase1, phase2);
|
DecoderFactory.transferJMBESettings(phase1, phase2);
|
||||||
@@ -147,6 +149,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
else if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase2 phase2)
|
else if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigP25Phase2 phase2)
|
||||||
{
|
{
|
||||||
mIgnoreDataCalls = phase2.getIgnoreDataCalls();
|
mIgnoreDataCalls = phase2.getIgnoreDataCalls();
|
||||||
|
mIgnoreEncryptedCalls = phase2.getIgnoreEncryptedCalls();
|
||||||
DecodeConfigP25Phase1 phase1 = new DecodeConfigP25Phase1();
|
DecodeConfigP25Phase1 phase1 = new DecodeConfigP25Phase1();
|
||||||
DecoderFactory.transferJMBESettings(phase2, phase1);
|
DecoderFactory.transferJMBESettings(phase2, phase1);
|
||||||
createPhase1TrafficChannels(phase2.getTrafficChannelPoolSize(), 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
|
//update the ending timestamp so that the duration value is correctly calculated
|
||||||
trackerTS2.updateDurationTraffic(timestamp);
|
trackerTS2.updateDurationTraffic(timestamp);
|
||||||
broadcast(trackerTS1);
|
broadcast(trackerTS2);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -1149,6 +1152,30 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
return completed;
|
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.
|
* 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);
|
addTracker(tracker, frequency, P25P1Message.TIMESLOT_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateEncrypted(tracker, serviceOptions);
|
||||||
|
|
||||||
//The tracked event can have an empty FROM identifier at start of call ... update here
|
//The tracked event can have an empty FROM identifier at start of call ... update here
|
||||||
tracker.addIdentifierIfMissing(from);
|
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
|
//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.
|
//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();
|
Channel trafficChannel = mAvailablePhase1TrafficChannelQueue.poll();
|
||||||
|
|
||||||
@@ -1293,6 +1323,22 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
return;
|
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 " +
|
String details = isDataChannelGrant ? "PHASE 1 DATA CHANNEL GRANT " : "PHASE 1 CHANNEL GRANT " +
|
||||||
(serviceOptions != null ? serviceOptions : "");
|
(serviceOptions != null ? serviceOptions : "");
|
||||||
|
|
||||||
@@ -1302,6 +1348,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
.identifiers(ic)
|
.identifiers(ic)
|
||||||
.build();
|
.build();
|
||||||
tracker = new P25ChannelEventTracker(event);
|
tracker = new P25ChannelEventTracker(event);
|
||||||
|
updateEncrypted(tracker, serviceOptions);
|
||||||
addTracker(tracker, frequency, P25P1Message.TIMESLOT_1);
|
addTracker(tracker, frequency, P25P1Message.TIMESLOT_1);
|
||||||
|
|
||||||
//Allocate a traffic channel for the downlink frequency if one isn't already allocated
|
//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);
|
broadcast(tracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateEncrypted(tracker, serviceOptions);
|
||||||
|
|
||||||
//update the ending timestamp so that the duration value is correctly calculated
|
//update the ending timestamp so that the duration value is correctly calculated
|
||||||
tracker.updateDurationControl(timestamp);
|
tracker.updateDurationControl(timestamp);
|
||||||
broadcast(tracker);
|
broadcast(tracker);
|
||||||
|
|
||||||
//Even though we have a tracked event, the initial channel grant may have been rejected. Check to see if there
|
//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.
|
//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) &&
|
if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !(mIgnoreDataCalls && isDataChannelGrant) &&
|
||||||
(getCurrentControlFrequency() != frequency))
|
!(mIgnoreEncryptedCalls && tracker.isEncrypted()) && (getCurrentControlFrequency() != frequency))
|
||||||
{
|
{
|
||||||
Channel trafficChannel = mAvailablePhase2TrafficChannelQueue.poll();
|
Channel trafficChannel = mAvailablePhase2TrafficChannelQueue.poll();
|
||||||
|
|
||||||
@@ -1404,7 +1456,25 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
tracker = new P25ChannelEventTracker(event);
|
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);
|
broadcast(tracker);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1417,6 +1487,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
tracker = new P25ChannelEventTracker(event);
|
tracker = new P25ChannelEventTracker(event);
|
||||||
|
updateEncrypted(tracker, serviceOptions);
|
||||||
addTracker(tracker, frequency, timeslot);
|
addTracker(tracker, frequency, timeslot);
|
||||||
|
|
||||||
//Allocate a traffic channel for the downlink frequency if one isn't already allocated
|
//Allocate a traffic channel for the downlink frequency if one isn't already allocated
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public abstract class DecodeConfigP25 extends DecodeConfigJMBE
|
|||||||
{
|
{
|
||||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||||
private boolean mIgnoreDataCalls = false;
|
private boolean mIgnoreDataCalls = false;
|
||||||
|
private boolean mIgnoreEncryptedCalls = false;
|
||||||
|
|
||||||
public DecodeConfigP25()
|
public DecodeConfigP25()
|
||||||
{
|
{
|
||||||
@@ -50,6 +51,25 @@ public abstract class DecodeConfigP25 extends DecodeConfigJMBE
|
|||||||
mIgnoreDataCalls = ignore;
|
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")
|
@JacksonXmlProperty(isAttribute = true, localName = "traffic_channel_pool_size")
|
||||||
public int getTrafficChannelPoolSize()
|
public int getTrafficChannelPoolSize()
|
||||||
|
|||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2026 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;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.EventBus;
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import io.github.dsheirer.controller.channel.Channel;
|
||||||
|
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||||
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
|
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.channel.P25Channel;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode;
|
||||||
|
import io.github.dsheirer.module.decode.DecoderFactory;
|
||||||
|
import io.github.dsheirer.module.decode.p25.reference.ServiceOptions;
|
||||||
|
import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions;
|
||||||
|
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the ignore encrypted calls option of the P25 traffic channel manager.
|
||||||
|
*/
|
||||||
|
public class P25TrafficChannelManagerEncryptedTest
|
||||||
|
{
|
||||||
|
private static final long BASE = 851_000_000L;
|
||||||
|
private static final long SPACING = 12_500L;
|
||||||
|
private static final int TALKGROUP_A = 100;
|
||||||
|
private static final int TALKGROUP_B = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures traffic channel start requests and decode events.
|
||||||
|
*/
|
||||||
|
private static class Harness
|
||||||
|
{
|
||||||
|
private final P25TrafficChannelManager mManager;
|
||||||
|
private final List<Long> mStartedFrequencies = new ArrayList<>();
|
||||||
|
private final List<IDecodeEvent> mEvents = new ArrayList<>();
|
||||||
|
|
||||||
|
Harness(DecodeConfigP25 config)
|
||||||
|
{
|
||||||
|
Channel parent = new Channel("control");
|
||||||
|
parent.setDecodeConfiguration(config);
|
||||||
|
mManager = new P25TrafficChannelManager(parent);
|
||||||
|
EventBus eventBus = new EventBus();
|
||||||
|
eventBus.register(this);
|
||||||
|
mManager.setInterModuleEventBus(eventBus);
|
||||||
|
mManager.addDecodeEventListener(mEvents::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void start(ChannelStartProcessingRequest request)
|
||||||
|
{
|
||||||
|
mStartedFrequencies.add(request.getChannel().getSourceConfiguration() instanceof
|
||||||
|
SourceConfigTuner sct ? sct.getFrequency() : -1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
String lastDetails()
|
||||||
|
{
|
||||||
|
return mEvents.get(mEvents.size() - 1).getDetails();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DecodeConfigP25Phase1 phase1(boolean ignoreEncrypted)
|
||||||
|
{
|
||||||
|
DecodeConfigP25Phase1 config = new DecodeConfigP25Phase1();
|
||||||
|
config.setIgnoreEncryptedCalls(ignoreEncrypted);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DecodeConfigP25Phase2 phase2(boolean ignoreEncrypted)
|
||||||
|
{
|
||||||
|
DecodeConfigP25Phase2 config = new DecodeConfigP25Phase2();
|
||||||
|
config.setIgnoreEncryptedCalls(ignoreEncrypted);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency band where the downlink frequency for channel number N is BASE + (N / timeslots) * SPACING
|
||||||
|
*/
|
||||||
|
private static IFrequencyBand band(int timeslots)
|
||||||
|
{
|
||||||
|
return new IFrequencyBand()
|
||||||
|
{
|
||||||
|
@Override public boolean isValid() {return true;}
|
||||||
|
@Override public int getIdentifier() {return timeslots == 1 ? 1 : 2;}
|
||||||
|
@Override public long getChannelSpacing() {return SPACING;}
|
||||||
|
@Override public long getBaseFrequency() {return BASE;}
|
||||||
|
@Override public int getBandwidth() {return 12500;}
|
||||||
|
@Override public long getTransmitOffset() {return 0;}
|
||||||
|
@Override public long getDownlinkFrequency(int channelNumber) {return BASE + (channelNumber / timeslots) * SPACING;}
|
||||||
|
@Override public long getUplinkFrequency(int channelNumber) {return getDownlinkFrequency(channelNumber);}
|
||||||
|
@Override public boolean isTDMA() {return timeslots > 1;}
|
||||||
|
@Override public int getTimeslotCount() {return timeslots;}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static APCO25Channel fdma(int channelNumber)
|
||||||
|
{
|
||||||
|
P25Channel channel = new P25Channel(1, channelNumber);
|
||||||
|
channel.setFrequencyBand(band(1));
|
||||||
|
return new APCO25Channel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param logicalChannel number (ie frequency index)
|
||||||
|
* @param timeslot 1 or 2
|
||||||
|
*/
|
||||||
|
private static APCO25Channel tdma(int logicalChannel, int timeslot)
|
||||||
|
{
|
||||||
|
P25Channel channel = new P25Channel(2, logicalChannel * 2 + (timeslot - 1));
|
||||||
|
channel.setFrequencyBand(band(2));
|
||||||
|
return new APCO25Channel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MutableIdentifierCollection ic(int talkgroup, Integer radio)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection();
|
||||||
|
mic.update(APCO25Talkgroup.create(talkgroup));
|
||||||
|
if(radio != null)
|
||||||
|
{
|
||||||
|
mic.update(APCO25RadioIdentifier.createFrom(radio));
|
||||||
|
}
|
||||||
|
return mic;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void p1Grant(Harness h, APCO25Channel channel, ServiceOptions so, int tg, Integer radio, long ts)
|
||||||
|
{
|
||||||
|
h.mManager.processP1ControlDirectedChannelGrant(channel, so, ic(tg, radio), Opcode.OSP_GROUP_VOICE_CHANNEL_GRANT, ts, "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void p1Update(Harness h, APCO25Channel channel, int tg, long ts)
|
||||||
|
{
|
||||||
|
h.mManager.processP1ControlAnnouncedTrafficUpdate(channel, null, ic(tg, null),
|
||||||
|
Opcode.OSP_GROUP_VOICE_CHANNEL_GRANT_UPDATE, ts, "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void p2Grant(Harness h, APCO25Channel channel, ServiceOptions so, int tg, Integer radio, long ts)
|
||||||
|
{
|
||||||
|
h.mManager.processP2ChannelGrant(channel, so, ic(tg, radio), MacOpcode.PHASE1_C0_GROUP_VOICE_CHANNEL_GRANT_EXPLICIT, ts, "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mirrors the decoder which uses empty (ie unencrypted) service options for updates that don't carry them.
|
||||||
|
*/
|
||||||
|
private static void p2Update(Harness h, APCO25Channel channel, int tg, long ts)
|
||||||
|
{
|
||||||
|
h.mManager.processP2ChannelUpdate(channel, new VoiceServiceOptions(0), ic(tg, null),
|
||||||
|
MacOpcode.PHASE1_42_GROUP_VOICE_CHANNEL_GRANT_UPDATE_IMPLICIT, ts, "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase1EncryptedGrantIsTunedWhenOptionDisabled()
|
||||||
|
{
|
||||||
|
Harness h = new Harness(phase1(false));
|
||||||
|
p1Grant(h, fdma(10), VoiceServiceOptions.createEncrypted(), TALKGROUP_A, 1, 1000);
|
||||||
|
assertEquals(List.of(BASE + 10 * SPACING), h.mStartedFrequencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase1EncryptedGrantIsIgnoredIncludingLaterUpdates()
|
||||||
|
{
|
||||||
|
Harness h = new Harness(phase1(true));
|
||||||
|
p1Grant(h, fdma(10), VoiceServiceOptions.createEncrypted(), TALKGROUP_A, 1, 1000);
|
||||||
|
p1Update(h, fdma(10), TALKGROUP_A, 1500);
|
||||||
|
p1Grant(h, fdma(10), VoiceServiceOptions.createEncrypted(), TALKGROUP_A, 1, 2000);
|
||||||
|
assertTrue(h.mStartedFrequencies.isEmpty());
|
||||||
|
assertTrue(h.lastDetails().startsWith("IGNORED: ENCRYPTED"), h.lastDetails());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase1ClearGrantIsTuned()
|
||||||
|
{
|
||||||
|
Harness h = new Harness(phase1(true));
|
||||||
|
p1Grant(h, fdma(10), VoiceServiceOptions.createUnencrypted(), TALKGROUP_A, 1, 1000);
|
||||||
|
assertEquals(List.of(BASE + 10 * SPACING), h.mStartedFrequencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase1DifferentTalkerWithClearGrantIsTuned()
|
||||||
|
{
|
||||||
|
Harness h = new Harness(phase1(true));
|
||||||
|
p1Grant(h, fdma(10), VoiceServiceOptions.createEncrypted(), TALKGROUP_A, 1, 1000);
|
||||||
|
p1Grant(h, fdma(10), VoiceServiceOptions.createUnencrypted(), TALKGROUP_A, 2, 1500);
|
||||||
|
assertEquals(List.of(BASE + 10 * SPACING), h.mStartedFrequencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase2EncryptedGrantIsIgnoredIncludingLaterUpdates()
|
||||||
|
{
|
||||||
|
Harness h = new Harness(phase2(true));
|
||||||
|
p2Grant(h, tdma(5, 1), VoiceServiceOptions.createEncrypted(), TALKGROUP_A, 1, 1000);
|
||||||
|
p2Update(h, tdma(5, 1), TALKGROUP_A, 1500);
|
||||||
|
assertTrue(h.mStartedFrequencies.isEmpty());
|
||||||
|
assertTrue(h.lastDetails().startsWith("IGNORED: ENCRYPTED"), h.lastDetails());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase2ClearCallOnOtherTimeslotTunesChannel()
|
||||||
|
{
|
||||||
|
Harness h = new Harness(phase2(true));
|
||||||
|
p2Grant(h, tdma(5, 1), VoiceServiceOptions.createEncrypted(), TALKGROUP_A, 1, 1000);
|
||||||
|
assertTrue(h.mStartedFrequencies.isEmpty());
|
||||||
|
p2Grant(h, tdma(5, 2), VoiceServiceOptions.createUnencrypted(), TALKGROUP_B, 2, 1100);
|
||||||
|
assertEquals(List.of(BASE + 5 * SPACING), h.mStartedFrequencies);
|
||||||
|
|
||||||
|
//Further updates on either timeslot don't request another channel
|
||||||
|
p2Update(h, tdma(5, 1), TALKGROUP_A, 1500);
|
||||||
|
p2Update(h, tdma(5, 2), TALKGROUP_B, 1500);
|
||||||
|
assertEquals(1, h.mStartedFrequencies.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase2EncryptedGrantOnAlreadyTunedChannelIsNotLabeledIgnored()
|
||||||
|
{
|
||||||
|
Harness h = new Harness(phase2(true));
|
||||||
|
p2Grant(h, tdma(5, 2), VoiceServiceOptions.createUnencrypted(), TALKGROUP_B, 2, 1000);
|
||||||
|
p2Grant(h, tdma(5, 1), VoiceServiceOptions.createEncrypted(), TALKGROUP_A, 1, 1100);
|
||||||
|
assertEquals(1, h.mStartedFrequencies.size());
|
||||||
|
assertTrue(h.lastDetails().startsWith("PHASE 2 CHANNEL GRANT"), h.lastDetails());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase2EncryptedGrantIsTunedWhenOptionDisabled()
|
||||||
|
{
|
||||||
|
Harness h = new Harness(phase2(false));
|
||||||
|
p2Grant(h, tdma(5, 1), VoiceServiceOptions.createEncrypted(), TALKGROUP_A, 1, 1000);
|
||||||
|
assertEquals(List.of(BASE + 5 * SPACING), h.mStartedFrequencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<IDecodeEvent> distinctEvents(Harness h, int timeslot)
|
||||||
|
{
|
||||||
|
Set<IDecodeEvent> events = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||||
|
for(IDecodeEvent event : h.mEvents)
|
||||||
|
{
|
||||||
|
if(event.getTimeslot() == timeslot)
|
||||||
|
{
|
||||||
|
events.add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase2IgnoredDataCallOnTimeslot2IsTrackedOnTimeslot2()
|
||||||
|
{
|
||||||
|
DecodeConfigP25Phase2 config = phase2(false);
|
||||||
|
config.setIgnoreDataCalls(true);
|
||||||
|
Harness h = new Harness(config);
|
||||||
|
h.mManager.processP1ControlDirectedChannelGrant(tdma(5, 2), null, ic(TALKGROUP_A, 1),
|
||||||
|
Opcode.OSP_GROUP_DATA_CHANNEL_GRANT, 1000, "test");
|
||||||
|
h.mManager.processP1ControlDirectedChannelGrant(tdma(5, 2), null, ic(TALKGROUP_A, 1),
|
||||||
|
Opcode.OSP_GROUP_DATA_CHANNEL_GRANT, 1500, "test");
|
||||||
|
assertTrue(h.mStartedFrequencies.isEmpty());
|
||||||
|
assertEquals(1, distinctEvents(h, 2).size(), "repeated grant should update the timeslot 2 event");
|
||||||
|
assertTrue(distinctEvents(h, 1).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase2DataChannelBroadcastsBothTimeslotEvents()
|
||||||
|
{
|
||||||
|
Harness h = new Harness(phase2(false));
|
||||||
|
h.mManager.processP2DataChannel(tdma(5, 1), 1000);
|
||||||
|
assertEquals(1, distinctEvents(h, 1).size());
|
||||||
|
assertEquals(1, distinctEvents(h, 2).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void phase2CopyKeepsTrafficChannelSettings()
|
||||||
|
{
|
||||||
|
DecodeConfigP25Phase2 original = phase2(true);
|
||||||
|
original.setIgnoreDataCalls(true);
|
||||||
|
original.setTrafficChannelPoolSize(7);
|
||||||
|
DecodeConfigP25Phase2 copy = (DecodeConfigP25Phase2)DecoderFactory.copy(original);
|
||||||
|
assertTrue(copy.getIgnoreDataCalls());
|
||||||
|
assertTrue(copy.getIgnoreEncryptedCalls());
|
||||||
|
assertEquals(7, copy.getTrafficChannelPoolSize());
|
||||||
|
|
||||||
|
DecodeConfigP25Phase2 defaults = (DecodeConfigP25Phase2)DecoderFactory.copy(phase2(false));
|
||||||
|
assertFalse(defaults.getIgnoreDataCalls());
|
||||||
|
assertFalse(defaults.getIgnoreEncryptedCalls());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user