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 a97b6943..5e67294b 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 @@ -192,6 +192,14 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList private final P25TrafficChannelManager mTrafficChannelManager; private ServiceOptions mCurrentServiceOptions; + /** + * Encryption state of the current call as signalled by the algorithm ID in the HDU header or LDU2 encryption sync + * parameters (null when not yet known for this call). Some radios set the encryption flag in the service options of + * the LDU1 voice channel user link control while transmitting clear voice (HDU and LDU2 report ALGID 0x80), so the + * algorithm ID takes precedence over the link control service options when it is known. + */ + private Boolean mCallEncryptedByAlgorithm = null; + /** * Constructs an APCO-25 decoder state with an optional traffic channel manager. * @param channel with configuration details @@ -419,6 +427,12 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(lcw instanceof IServiceOptionsProvider sop) { serviceOptions = sop.getServiceOptions(); + + //Trust the HDU/LDU2 algorithm ID over the link control service options encryption flag when known. + if(serviceOptions != null && serviceOptions.isEncrypted() && Boolean.FALSE.equals(mCallEncryptedByAlgorithm)) + { + serviceOptions = VoiceServiceOptions.createUnencrypted(serviceOptions); + } } else if(lcw.isEncrypted()) { @@ -838,6 +852,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList Identifier radio = getIdentifierCollection().getFromIdentifier(); + mCallEncryptedByAlgorithm = headerData.isEncryptedAudio(); mTrafficChannelManager.processP1TrafficCallStart(getCurrentFrequency(), talkgroup, radio, headerData.getEncryptionKey(), mCurrentServiceOptions, getCurrentChannel(), message.getTimestamp()); @@ -880,6 +895,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(esp != null && esp.isValid()) { getIdentifierCollection().update(esp.getIdentifiers()); + mCallEncryptedByAlgorithm = esp.isEncryptedAudio(); if(esp.isEncryptedAudio()) { @@ -903,6 +919,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList */ private void processTDU(P25P1Message message) { + mCallEncryptedByAlgorithm = null; mTrafficChannelManager.processP1TrafficCallEnd(getCurrentFrequency(), message.getTimestamp(), "TDU:" + message); broadcast(new DecoderStateEvent(this, Event.DECODE, State.ACTIVE)); } @@ -921,6 +938,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList if(lcw != null && lcw.isValid()) { + mCallEncryptedByAlgorithm = null; mTrafficChannelManager.processP1TrafficCallEnd(getCurrentFrequency(), message.getTimestamp(), "TDULC:" + message); 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/reference/VoiceServiceOptions.java b/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java index f7b1c6ad..226ee6fc 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/reference/VoiceServiceOptions.java @@ -80,4 +80,13 @@ public class VoiceServiceOptions extends ServiceOptions { return new VoiceServiceOptions(4); } + + /** + * Creates a copy of the supplied service options with the encryption flag cleared and all other options unchanged. + * @param serviceOptions to copy + */ + public static VoiceServiceOptions createUnencrypted(ServiceOptions serviceOptions) + { + return new VoiceServiceOptions(serviceOptions.mServiceOptions & ~ENCRYPTION_FLAG); + } }