Compare commits

...

7 Commits

Author SHA1 Message Date
Denny
5efd89632e Ensures that any leftover cached LDU1 message is reset when the call ends. 2017-05-28 07:30:05 -04:00
Denny
1dc3863489 Updates build to 0.3.0 Beta 12 2017-05-28 07:22:43 -04:00
Denny Sheirer
441286ba13 Merge pull request #247 from DSheirer/244-ldu1-encryption-flag
244 Prevent P25 Encrypted Audio Decoding/Playback
2017-05-28 11:20:20 +00:00
Denny
e4323df347 Resolves issue #244 - updates the P25 IMBE audio module to correctly
detect encrypted audio state so that encrypted audio is not (incorrectly)
decoded and played back to the user.

Processes HDU and LDU2 messages to identify encrypted call state.  Once
established, all subsequent LDU1/2 messages are decoded only if the call
is unencrypted.  If an LDU1 message is received out-of-sequence, meaning
that the initial HDU message was missed, then the LDU1 is cached until
we receive an LDU2 message and correctly determine the call encryption
state.
2017-05-28 07:17:09 -04:00
Denny
a872e0d664 Updates version to Beta 0.3.0 Beta 11 2017-05-20 07:24:27 -04:00
Denny Sheirer
fbc92cfe31 Merge pull request #246 from DSheirer/245-sound-card-source-issue
Using sound card or mixer as a source throws an error
2017-05-20 10:55:23 +00:00
Denny
1a432197ac Resolves #245 issue with NPE for frequency change event listener when using the sound card/mixer as a source. 2017-05-20 06:51:53 -04:00
8 changed files with 317 additions and 255 deletions

View File

@@ -7,7 +7,7 @@
<property name="major" value="0"/> <property name="major" value="0"/>
<property name="minor" value="3"/> <property name="minor" value="3"/>
<property name="patch" value="0"/> <property name="patch" value="0"/>
<property name="release" value="-beta10"/> <property name="release" value="-beta12"/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/> <property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<!-- Build properties --> <!-- Build properties -->

View File

@@ -320,7 +320,13 @@ public class ProcessingChain implements IChannelEventListener
if(module instanceof IFrequencyChangeListener) if(module instanceof IFrequencyChangeListener)
{ {
mFrequencyChangeEventBroadcaster.addListener(((IFrequencyChangeListener) module).getFrequencyChangeListener()); Listener<FrequencyChangeEvent> frequencyChangeEventListener =
((IFrequencyChangeListener)module).getFrequencyChangeListener();
if(frequencyChangeEventListener != null)
{
mFrequencyChangeEventBroadcaster.addListener(frequencyChangeEventListener);
}
} }
if(module instanceof IMessageListener) if(module instanceof IMessageListener)

View File

@@ -421,7 +421,7 @@ public class P25DecoderState extends DecoderState
mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL) mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(mCurrentChannel) .channel(mCurrentChannel)
.details((hdu.isEncrypted() ? "ENCRYPTED " : "")) .details((hdu.isEncryptedAudio() ? "ENCRYPTED " : ""))
.frequency(mCurrentChannelFrequency) .frequency(mCurrentChannelFrequency)
.to(to) .to(to)
.build(); .build();
@@ -1107,7 +1107,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcu.getChannelA()) .channel(gvcu.getChannelA())
.details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcu.getDownlinkFrequencyA()) .frequency(gvcu.getDownlinkFrequencyA())
.to(userA) .to(userA)
.build()); .build());
@@ -1122,7 +1122,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcu.getChannelB()) .channel(gvcu.getChannelB())
.details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcu.getDownlinkFrequencyB()) .frequency(gvcu.getDownlinkFrequencyB())
.to(userB) .to(userB)
.build()); .build());
@@ -1151,7 +1151,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcue.getTransmitChannel()) .channel(gvcue.getTransmitChannel())
.details((gvcue.isEncrypted() ? "ENCRYPTED" : "")) .details((gvcue.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcue.getDownlinkFrequency()) .frequency(gvcue.getDownlinkFrequency())
.to(group) .to(group)
.build()); .build());
@@ -1187,7 +1187,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null) if(mCurrentCallEvent.getDetails() == null)
{ {
mCurrentCallEvent.setDetails( mCurrentCallEvent.setDetails(
(gvcuser.isEncrypted() ? "ENCRYPTED " : "") + (gvcuser.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(gvcuser.isEmergency() ? "EMERGENCY " : "")); (gvcuser.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent); broadcast(mCurrentCallEvent);
@@ -1399,7 +1399,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null) if(mCurrentCallEvent.getDetails() == null)
{ {
mCurrentCallEvent.setDetails( mCurrentCallEvent.setDetails(
(tivcu.isEncrypted() ? "ENCRYPTED " : "") + (tivcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(tivcu.isEmergency() ? "EMERGENCY " : "")); (tivcu.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent); broadcast(mCurrentCallEvent);
@@ -1482,7 +1482,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null) if(mCurrentCallEvent.getDetails() == null)
{ {
mCurrentCallEvent.setDetails( mCurrentCallEvent.setDetails(
(uuvcu.isEncrypted() ? "ENCRYPTED " : "") + (uuvcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(uuvcu.isEmergency() ? "EMERGENCY " : "")); (uuvcu.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent); broadcast(mCurrentCallEvent);

View File

@@ -12,6 +12,9 @@ import jmbe.iface.AudioConverter;
import message.IMessageListener; import message.IMessageListener;
import message.Message; import message.Message;
import module.Module; import module.Module;
import module.decode.p25.message.hdu.HDUMessage;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.ldu.LDU2Message;
import module.decode.p25.message.ldu.LDUMessage; import module.decode.p25.message.ldu.LDUMessage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -25,14 +28,18 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class); private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class);
private static final String IMBE_CODEC = "IMBE"; private static final String IMBE_CODEC = "IMBE";
private static boolean mLibraryLoadStatusLogged = false; private static boolean mLibraryLoadStatusLogged = false;
private boolean mCanConvertAudio = false; private boolean mCanConvertAudio = false;
private boolean mEncryptedCall = false;
private boolean mEncryptedCallStateEstablished = false;
private AudioConverter mAudioConverter; private AudioConverter mAudioConverter;
private Listener<AudioPacket> mAudioPacketListener; private Listener<AudioPacket> mAudioPacketListener;
private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f); private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
private Metadata mMetadata; private Metadata mMetadata;
private LDU1Message mCachedLDU1Message = null;
public P25AudioModule(Metadata metadata) public P25AudioModule(Metadata metadata)
{ {
@@ -76,21 +83,60 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
} }
/** /**
* Primary processing method for p25 imbe audio frame messages. Each LDU * Processes call header (HDU) and voice frame (LDU1/LDU2) messages to decode audio and to determine the
* audio message contains 9 imbe voice frames. Each frame is converted to * encrypted audio status of a call event. Only the HDU and LDU2 messages convey encrypted call status. If an
* audio when the JMBE library is loaded, processed by auto-gain, and * LDU1 message is received without a preceding HDU message, then the LDU1 message is cached until the first
* broadcast to the audio packet listener. * LDU2 message is received and the encryption state can be determined. Both the LDU1 and the LDU2 message are
* then processed for audio if the call is unencrypted.
*/ */
public void receive(Message message) public void receive(Message message)
{ {
if(mCanConvertAudio && mAudioPacketListener != null) if(mCanConvertAudio && mAudioPacketListener != null)
{
if(mEncryptedCallStateEstablished)
{ {
if(message instanceof LDUMessage) if(message instanceof LDUMessage)
{ {
LDUMessage ldu = (LDUMessage) message; processAudio((LDUMessage)message);
}
}
else
{
if(message instanceof HDUMessage)
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = ((HDUMessage)message).isEncryptedAudio();
}
else if(message instanceof LDU1Message)
{
//When we receive an LDU1 message with 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;
}
else if(message instanceof LDU2Message)
{
mEncryptedCallStateEstablished = true;
LDU2Message ldu2 = (LDU2Message)message;
mEncryptedCall = ldu2.isEncryptedAudio();
/* Only process unencrypted audio frames */ if(mCachedLDU1Message != null)
if (!(ldu.isValid() && ldu.isEncrypted())) {
processAudio(mCachedLDU1Message);
mCachedLDU1Message = null;
}
processAudio(ldu2);
}
}
}
}
/**
* Processes an audio packet by decoding the IMBE audio frames and rebroadcasting them as PCM audio packets.
*/
private void processAudio(LDUMessage ldu)
{
if(!mEncryptedCall)
{ {
for(byte[] frame : ldu.getIMBEFrames()) for(byte[] frame : ldu.getIMBEFrames())
{ {
@@ -101,7 +147,9 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf())); mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
} }
} }
} else
{
//Encrypted audio processing not implemented
} }
} }
@@ -196,20 +244,26 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
} }
/** /**
* Wrapper for squelch state listener. Internally, the P25 audio module * Wrapper for squelch state to process end of call actions. At call end the encrypted call state established
* doesn't have a squelch state. If there are IMBE audio frames, we have * flag is reset so that the encrypted audio state for the next call can be properly detected and we send an
* audio. We use this listener to signal to the recorder manager that the * END audio packet so that downstream processors like the audio recorder can properly close out a call sequence.
* channel state is resetting and it should end the recording.
*/ */
public class SquelchStateListener implements Listener<SquelchState> public class SquelchStateListener implements Listener<SquelchState>
{ {
@Override @Override
public void receive(SquelchState state) public void receive(SquelchState state)
{ {
if (state == SquelchState.SQUELCH && mAudioPacketListener != null) if(state == SquelchState.SQUELCH)
{
if(mAudioPacketListener != null)
{ {
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf())); mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
} }
mEncryptedCallStateEstablished = false;
mEncryptedCall = false;
mCachedLDU1Message = null;
}
} }
} }
} }

View File

@@ -245,7 +245,7 @@ public class HDUMessage extends P25Message
return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) );
} }
public boolean isEncrypted() public boolean isEncryptedAudio()
{ {
return getEncryption() != Encryption.UNENCRYPTED; return getEncryption() != Encryption.UNENCRYPTED;
} }

View File

@@ -1,24 +1,22 @@
package module.decode.p25.message.ldu; package module.decode.p25.message.ldu;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList; import alias.AliasList;
import bits.BinaryMessage; import bits.BinaryMessage;
import edac.CRC; import edac.CRC;
import edac.Hamming10; import edac.Hamming10;
import edac.ReedSolomon_63_47_17; import edac.ReedSolomon_63_47_17;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LDU1Message extends LDUMessage public class LDU1Message extends LDUMessage
{ {
private final static Logger mLog = LoggerFactory.getLogger(LDU1Message.class); private final static Logger mLog = LoggerFactory.getLogger(LDU1Message.class);
public static final int ENCRYPTION_FLAG = 352; public static final int ENCRYPTED_LINK_CONTROL_WORD_FLAG = 352;
public static final int IMPLICIT_VENDOR_FLAG = 353; public static final int IMPLICIT_VENDOR_FLAG = 353;
public static final int[] OPCODE = {354, 355, 356, 357, 362, 363}; public static final int[] OPCODE = {354, 355, 356, 357, 362, 363};
public static final int[] VENDOR = {364, 365, 366, 367, 372, 373, 374, 375}; public static final int[] VENDOR = {364, 365, 366, 367, 372, 373, 374, 375};
@@ -81,10 +79,14 @@ public class LDU1Message extends LDUMessage
mCRC = message.mCRC; mCRC = message.mCRC;
} }
@Override /**
public boolean isEncrypted() * Indicates if the contents of the Link Control Word are encrypted.
*
* @return true if the contents of the LCW are encrypted.
*/
public boolean isEncryptedLinkControlWord()
{ {
return mMessage.get( ENCRYPTION_FLAG ); return mMessage.get(ENCRYPTED_LINK_CONTROL_WORD_FLAG);
} }
private void checkCRC() private void checkCRC()
@@ -197,7 +199,7 @@ public class LDU1Message extends LDUMessage
sb.append(super.getMessageStub()); sb.append(super.getMessageStub());
if( isEncrypted() ) if(isEncryptedLinkControlWord())
{ {
sb.append("ENCRYPTED"); sb.append("ENCRYPTED");
} }

View File

@@ -73,8 +73,10 @@ public class LDU2Message extends LDUMessage
checkCRC(); checkCRC();
} }
@Override /**
public boolean isEncrypted() * Indicates if the audio stream is encrypted
*/
public boolean isEncryptedAudio()
{ {
Encryption encryption = getEncryption(); Encryption encryption = getEncryption();

View File

@@ -35,8 +35,6 @@ public abstract class LDUMessage extends P25Message
super( message, duid, aliasList ); super( message, duid, aliasList );
} }
public abstract boolean isEncrypted();
@Override @Override
public String getMessage() public String getMessage()
{ {