From 86cb5b2f4b6b49c990f4eda2e775995105c30d9c Mon Sep 17 00:00:00 2001 From: "dennis.sheirer" Date: Tue, 23 Dec 2014 22:43:55 +0000 Subject: [PATCH] P25 work in progress --- src/crc/CRCP25.java | 4 +- src/decode/DecoderType.java | 2 +- src/decode/p25/P25ChannelState.java | 642 +++++++++++------- src/decode/p25/P25Decoder.java | 13 +- src/decode/p25/message/hdu/HDUMessage.java | 44 ++ src/decode/p25/message/ldu/LDU1Message.java | 44 ++ src/decode/p25/message/ldu/LDU2Message.java | 44 ++ .../pdu/UnitToUnitChannelGrantExtended.java | 2 +- .../data/GroupDataChannelGrantExtended.java | 2 +- .../voice/GroupVoiceChannelGrantExplicit.java | 2 +- ...ToUnitVoiceChannelGrantUpdateExtended.java | 2 +- src/decode/p25/message/tdu/TDULCMessage.java | 44 ++ .../p25/message/tsbk/TSBKMessageFactory.java | 3 + .../osp/control/AuthenticationCommand.java | 13 + src/decode/p25/reference/DataUnitID.java | 2 +- src/dsp/fsk/P25MessageFramer.java | 12 +- src/instrument/gui/DecoderViewFrame.java | 5 + src/instrument/tap/TapType.java | 1 + src/instrument/tap/stream/DibitTap.java | 60 ++ .../tap/stream/DibitTapViewPanel.java | 190 ++++++ 20 files changed, 865 insertions(+), 266 deletions(-) create mode 100644 src/decode/p25/message/hdu/HDUMessage.java create mode 100644 src/decode/p25/message/ldu/LDU1Message.java create mode 100644 src/decode/p25/message/ldu/LDU2Message.java create mode 100644 src/decode/p25/message/tdu/TDULCMessage.java create mode 100644 src/instrument/tap/stream/DibitTap.java create mode 100644 src/instrument/tap/stream/DibitTapViewPanel.java diff --git a/src/crc/CRCP25.java b/src/crc/CRCP25.java index ac928016..2560f99d 100644 --- a/src/crc/CRCP25.java +++ b/src/crc/CRCP25.java @@ -220,7 +220,9 @@ public class CRCP25 0x00080000l, 0x00100000l, 0x00200000l, 0x00400000l, 0x00800000l, 0x01000000l, 0x02000000l, 0x04000000l, 0x08000000l, 0x10000000l, 0x20000000l, 0x40000000l, 0x80000000l - }; + }; + + /** * Performs CRC check and corrects single-bit errors against the message. diff --git a/src/decode/DecoderType.java b/src/decode/DecoderType.java index c300b6e2..f6013176 100644 --- a/src/decode/DecoderType.java +++ b/src/decode/DecoderType.java @@ -67,7 +67,7 @@ public enum DecoderType DecoderType.LTR_STANDARD, DecoderType.MPT1327, DecoderType.NBFM, -// DecoderType.P25_PHASE1, + DecoderType.P25_PHASE1, DecoderType.PASSPORT ); } diff --git a/src/decode/p25/P25ChannelState.java b/src/decode/p25/P25ChannelState.java index 1fdc8ac0..84057a6c 100644 --- a/src/decode/p25/P25ChannelState.java +++ b/src/decode/p25/P25ChannelState.java @@ -22,6 +22,9 @@ import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import message.Message; import alias.Alias; import alias.AliasList; @@ -92,8 +95,14 @@ import decode.p25.reference.Vendor; public class P25ChannelState extends ChannelState { + private final static Logger mLog = LoggerFactory.getLogger( P25ChannelState.class ); + private HashMap mRegistrations = new HashMap(); + private String mLastCommandEventID; + private String mLastPageEventID; + private String mLastQueryEventID; private String mLastRegistrationEventID; + private String mLastResponseEventID; private ArrayList mActiveCalls = new ArrayList(); @@ -269,68 +278,100 @@ public class P25ChannelState extends ChannelState private void processCommand( TSBKMessage message ) { + CallEvent event = null; + switch( message.getOpcode() ) { case AUTHENTICATION_COMMAND: AuthenticationCommand ac = (AuthenticationCommand)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.COMMAND ) + if( mLastCommandEventID == null || !mLastCommandEventID + .contentEquals( ac.getFullTargetID() )) + { + event = new P25CallEvent.Builder( CallEventType.COMMAND ) .aliasList( mAliasList ) .details( "AUTHENTICATE" ) .to( ac.getWACN() + "-" + ac.getSystemID() + "-" + ac.getTargetID() ) - .build() ); + .build(); + + mLastCommandEventID = ac.getFullTargetID(); + } break; case EXTENDED_FUNCTION_COMMAND: ExtendedFunctionCommand efc = (ExtendedFunctionCommand)message; - - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.FUNCTION ) - .aliasList( mAliasList ) - .details( "EXTENDED FUNCTION: " + - efc.getExtendedFunction().getLabel() ) - .from( efc.getSourceAddress() ) - .to( efc.getTargetAddress() ) - .build() ); + + if( mLastCommandEventID == null || !mLastCommandEventID + .contentEquals( efc.getTargetAddress() ) ) + { + event = new P25CallEvent.Builder( CallEventType.FUNCTION ) + .aliasList( mAliasList ) + .details( "EXTENDED FUNCTION: " + + efc.getExtendedFunction().getLabel() ) + .from( efc.getSourceAddress() ) + .to( efc.getTargetAddress() ) + .build(); + + mLastCommandEventID = efc.getTargetAddress(); + } break; case RADIO_UNIT_MONITOR_COMMAND: RadioUnitMonitorCommand rumc = (RadioUnitMonitorCommand)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.COMMAND ) + if( mLastCommandEventID == null || !mLastCommandEventID + .contentEquals( rumc.getTargetAddress() ) ) + { + event = new P25CallEvent.Builder( CallEventType.COMMAND ) .aliasList( mAliasList ) .details( "RADIO UNIT MONITOR" ) .from( rumc.getSourceAddress() ) .to( rumc.getTargetAddress() ) - .build() ); + .build(); + + mLastCommandEventID = rumc.getTargetAddress(); + } break; case ROAMING_ADDRESS_COMMAND: RoamingAddressCommand rac = (RoamingAddressCommand)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( mAliasList ) - .details( rac.getStackOperation().name() + - " ROAMING ADDRESS " + rac.getWACN() + "-" + - rac.getSystemID() ) - .to( rac.getTargetID() ) - .build() ); + if( mLastCommandEventID == null || !mLastCommandEventID + .contentEquals( rac.getTargetID() ) ) + { + event = new P25CallEvent.Builder( CallEventType.COMMAND ) + .aliasList( mAliasList ) + .details( rac.getStackOperation().name() + + " ROAMING ADDRESS " + rac.getWACN() + "-" + + rac.getSystemID() ) + .to( rac.getTargetID() ) + .build(); + + mLastCommandEventID = rac.getTargetID(); + } break; case UNIT_REGISTRATION_COMMAND: UnitRegistrationCommand urc = (UnitRegistrationCommand)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( mAliasList ) - .details( "REGISTER" ) - .from( urc.getSourceAddress() ) - .to( urc.getTargetAddress() ) - .build() ); + if( mLastCommandEventID == null || !mLastCommandEventID + .contentEquals( urc.getTargetAddress() ) ) + { + event = new P25CallEvent.Builder( CallEventType.COMMAND ) + .aliasList( mAliasList ) + .details( "REGISTER" ) + .from( urc.getSourceAddress() ) + .to( urc.getTargetAddress() ) + .build(); + + mLastCommandEventID = urc.getTargetAddress(); + } break; default: break; } + + if( event != null ) + { + mCallEventModel.add( event ); + } } private void processMessage( TSBKMessage message ) @@ -366,120 +407,159 @@ public class P25ChannelState extends ChannelState private void processQuery( TSBKMessage message ) { + CallEvent event = null; + switch( message.getOpcode() ) { case GROUP_AFFILIATION_QUERY: GroupAffiliationQuery gaq = (GroupAffiliationQuery)message; - - CallEvent event = - new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( mAliasList ) - .details( "GROUP AFFILIATION" ) - .from( gaq.getSourceAddress() ) - .to( gaq.getTargetAddress() ) - .build(); - mCallEventModel.add( event ); + if( mLastQueryEventID == null || !mLastQueryEventID + .contentEquals( gaq.getTargetAddress() ) ) + { + event = new P25CallEvent.Builder( CallEventType.QUERY ) + .aliasList( mAliasList ) + .details( "GROUP AFFILIATION" ) + .from( gaq.getSourceAddress() ) + .to( gaq.getTargetAddress() ) + .build(); + } break; case STATUS_QUERY: StatusQuery sq = (StatusQuery)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( mAliasList ) - .details( "STATUS QUERY" ) - .from( sq.getSourceAddress() ) - .to( sq.getTargetAddress() ) - .build() ); + if( mLastQueryEventID == null || !mLastQueryEventID + .contentEquals( sq.getTargetAddress() ) ) + { + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.QUERY ) + .aliasList( mAliasList ) + .details( "STATUS QUERY" ) + .from( sq.getSourceAddress() ) + .to( sq.getTargetAddress() ) + .build() ); + } break; default: break; } + + if( event != null ) + { + mCallEventModel.add( event ); + + mLastQueryEventID = event.getToID(); + } } private void processQuery( PDUMessage message ) { + CallEvent event = null; + switch( message.getOpcode() ) { case GROUP_AFFILIATION_QUERY: GroupAffiliationQueryExtended gaqe = (GroupAffiliationQueryExtended)message; - CallEvent event = - new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( mAliasList ) - .details( "GROUP AFFILIATION" ) - .from( gaqe.getWACN() + "-" + gaqe.getSystemID() + - "-" + gaqe.getSourceID() ) - .to( gaqe.getTargetAddress() ) - .build(); - - mCallEventModel.add( event ); + if( mLastQueryEventID == null || !gaqe.getTargetAddress() + .contentEquals( mLastQueryEventID ) ) + { + event = new P25CallEvent.Builder( CallEventType.QUERY ) + .aliasList( mAliasList ) + .details( "GROUP AFFILIATION" ) + .from( gaqe.getWACN() + "-" + gaqe.getSystemID() + + "-" + gaqe.getSourceID() ) + .to( gaqe.getTargetAddress() ) + .build(); + } break; case STATUS_QUERY: StatusQueryExtended sq = (StatusQueryExtended)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( mAliasList ) - .details( "STATUS QUERY" ) - .from( sq.getSourceWACN() + "-" + - sq.getSourceSystemID() + "-" + - sq.getSourceID() ) - .to( sq.getTargetAddress() ) - .build() ); + if( mLastQueryEventID == null || !sq.getTargetAddress() + .contentEquals( mLastQueryEventID ) ) + { + event = new P25CallEvent.Builder( CallEventType.QUERY ) + .aliasList( mAliasList ) + .details( "STATUS QUERY" ) + .from( sq.getSourceWACN() + "-" + + sq.getSourceSystemID() + "-" + + sq.getSourceID() ) + .to( sq.getTargetAddress() ) + .build(); + } break; default: break; } + + if( event != null ) + { + mCallEventModel.add( event ); + + mLastQueryEventID = event.getToID(); + } } private void processResponse( TSBKMessage message ) { + CallEvent event = null; + switch( message.getOpcode() ) { case ACKNOWLEDGE_RESPONSE: AcknowledgeResponse ar = (AcknowledgeResponse)message; - CallEvent event = - new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( mAliasList ) - .details( "ACKNOWLEDGE" ) - .to( ar.getTargetAddress() ) - .build(); - - if( ar.hasAdditionalInformation() ) + if( mLastResponseEventID == null || !ar.getTargetAddress() + .contentEquals( mLastResponseEventID ) ) { - if( ar.hasExtendedAddress() ) + event = new P25CallEvent.Builder( CallEventType.RESPONSE ) + .aliasList( mAliasList ) + .details( "ACKNOWLEDGE" ) + .to( ar.getTargetAddress() ) + .build(); + + if( ar.hasAdditionalInformation() ) { - event.setToID( ar.getWACN() + "-" + ar.getSystemID() + "-" + - ar.getTargetAddress() ); - } - else - { - event.setFromID( ar.getFromID() ); + if( ar.hasExtendedAddress() ) + { + event.setToID( ar.getWACN() + "-" + ar.getSystemID() + "-" + + ar.getTargetAddress() ); + } + else + { + event.setFromID( ar.getFromID() ); + } } + + mLastResponseEventID = ar.getTargetAddress(); } - - mCallEventModel.add( event ); break; case DENY_RESPONSE: DenyResponse dr = (DenyResponse)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( mAliasList ) - .details( "DENY REASON: " + dr.getReason().name() + - " REQUESTED: " + dr.getServiceType().name() ) - .from( dr.getSourceAddress() ) - .to( dr.getTargetAddress() ) - .build() ); + if( mLastResponseEventID == null || !dr.getTargetAddress() + .contentEquals( mLastResponseEventID ) ) + { + event = new P25CallEvent.Builder( CallEventType.RESPONSE ) + .aliasList( mAliasList ) + .details( "DENY REASON: " + dr.getReason().name() + + " REQUESTED: " + dr.getServiceType().name() ) + .from( dr.getSourceAddress() ) + .to( dr.getTargetAddress() ) + .build(); + + mLastResponseEventID = dr.getTargetAddress(); + } break; case GROUP_AFFILIATION_RESPONSE: GroupAffiliationResponse gar = (GroupAffiliationResponse)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.RESPONSE ) + if( mLastResponseEventID == null || !gar.getTargetAddress() + .contentEquals( mLastResponseEventID ) ) + { + event = new P25CallEvent.Builder( CallEventType.RESPONSE ) .aliasList( mAliasList ) .details( "AFFILIATION:" + gar.getResponse().name() + " FOR " + gar.getAffiliationScope() + @@ -487,8 +567,10 @@ public class P25ChannelState extends ChannelState " ANNOUNCEMENT GROUP:" + gar.getAnnouncementGroupAddress() ) .to( gar.getTargetAddress() ) - .build() ); + .build(); + mLastResponseEventID = gar.getTargetAddress(); + } break; case LOCATION_REGISTRATION_RESPONSE: LocationRegistrationResponse lrr = @@ -500,7 +582,7 @@ public class P25ChannelState extends ChannelState System.currentTimeMillis() ); } - if( mLastRegistrationEventID != null && + if( mLastRegistrationEventID == null || !mLastRegistrationEventID.contentEquals( lrr.getTargetAddress() ) ) { mCallEventModel.add( @@ -512,45 +594,61 @@ public class P25ChannelState extends ChannelState .from( lrr.getTargetAddress() ) .to( lrr.getGroupAddress() ) .build() ); + + mLastRegistrationEventID = lrr.getTargetAddress(); } - - mLastRegistrationEventID = lrr.getTargetAddress(); break; case PROTECTION_PARAMETER_UPDATE: ProtectionParameterUpdate ppu = (ProtectionParameterUpdate)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( mAliasList ) - .details( "USE ENCRYPTION ALGORITHM:" + - ppu.getAlgorithm().name() + " KEY:" + - ppu.getKeyID() ) - .to( ppu.getTargetAddress() ) - .build() ); + if( mLastResponseEventID == null || !ppu.getTargetAddress() + .contentEquals( mLastResponseEventID ) ) + { + event = new P25CallEvent.Builder( CallEventType.RESPONSE ) + .aliasList( mAliasList ) + .details( "USE ENCRYPTION ALGORITHM:" + + ppu.getAlgorithm().name() + " KEY:" + + ppu.getKeyID() ) + .to( ppu.getTargetAddress() ) + .build(); + + mLastResponseEventID = ppu.getTargetAddress(); + } break; case QUEUED_RESPONSE: QueuedResponse qr = (QueuedResponse)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( mAliasList ) - .details( "QUEUED REASON: " + qr.getReason().name() + - " REQUESTED: " + qr.getServiceType().name() ) - .from( qr.getSourceAddress() ) - .to( qr.getTargetAddress() ) - .build() ); + if( mLastResponseEventID == null || !qr.getTargetAddress() + .contentEquals( mLastResponseEventID ) ) + { + event = new P25CallEvent.Builder( CallEventType.RESPONSE ) + .aliasList( mAliasList ) + .details( "QUEUED REASON: " + qr.getReason().name() + + " REQUESTED: " + qr.getServiceType().name() ) + .from( qr.getSourceAddress() ) + .to( qr.getTargetAddress() ) + .build(); + + mLastResponseEventID = qr.getTargetAddress(); + } break; case STATUS_UPDATE: StatusUpdate su = (StatusUpdate)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( mAliasList ) - .details( "STATUS USER: " + su.getUserStatus() + - " UNIT: " + su.getUnitStatus() ) - .from( su.getSourceAddress() ) - .to( su.getTargetAddress() ) - .build() ); + if( mLastResponseEventID == null || !su.getTargetAddress() + .contentEquals( mLastResponseEventID ) ) + { + event = new P25CallEvent.Builder( CallEventType.RESPONSE ) + .aliasList( mAliasList ) + .details( "STATUS USER: " + su.getUserStatus() + + " UNIT: " + su.getUnitStatus() ) + .from( su.getSourceAddress() ) + .to( su.getTargetAddress() ) + .build(); + + mLastResponseEventID = su.getTargetAddress(); + } + break; case UNIT_REGISTRATION_RESPONSE: UnitRegistrationResponse urr = (UnitRegistrationResponse)message; @@ -561,26 +659,26 @@ public class P25ChannelState extends ChannelState System.currentTimeMillis() ); } - if( mLastRegistrationEventID != null && + if( mLastRegistrationEventID == null || !mLastRegistrationEventID.contentEquals( urr.getSourceAddress() ) ) { mCallEventModel.add( new P25CallEvent.Builder( CallEventType.REGISTER ) .aliasList( mAliasList ) .details( "REGISTRATION:" + urr.getResponse().name() + - " SYSTEM: " + urr.getSystemID() + "-" + + " SYSTEM: " + urr.getSystemID() + " SOURCE ID: " + urr.getSourceID() ) .from( urr.getSourceAddress() ) .build() ); + + mLastRegistrationEventID = urr.getSourceAddress(); } - - mLastRegistrationEventID = urr.getSourceAddress(); break; case UNIT_DEREGISTRATION_ACKNOWLEDGE: UnitDeregistrationAcknowledge udr = (UnitDeregistrationAcknowledge)message; - if( mLastRegistrationEventID != null && + if( mLastRegistrationEventID == null || !mLastRegistrationEventID.contentEquals( udr.getSourceID() ) ) { @@ -605,43 +703,53 @@ public class P25ChannelState extends ChannelState { mRegistrations.remove( key ); } + + mLastRegistrationEventID = udr.getSourceID(); } - - mLastRegistrationEventID = udr.getSourceID(); break; default: break; } + + if( event != null ) + { + mCallEventModel.add( event ); + } } private void processResponse( PDUMessage message ) { + CallEvent event = null; + switch( message.getOpcode() ) { case GROUP_AFFILIATION_RESPONSE: GroupAffiliationResponseExtended gar = (GroupAffiliationResponseExtended)message; - CallEvent event = - new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( mAliasList ) - .details( "AFFILIATION:" + gar.getResponse().name() + - " FOR GROUP:" + gar.getGroupWACN() + "-" + - gar.getGroupSystemID() + "-" + - gar.getGroupID() + " ANNOUNCEMENT GROUP:" + - gar.getAnnouncementGroupID() ) - .from( gar.getSourceWACN() + "-" + - gar.getSourceSystemID() + "-" + - gar.getSourceID() ) - .to( gar.getTargetAddress() ) - .build(); - - mCallEventModel.add( event ); + if( mLastResponseEventID == null || !gar.getTargetAddress() + .contentEquals( mLastResponseEventID ) ) + { + event = new P25CallEvent.Builder( CallEventType.RESPONSE ) + .aliasList( mAliasList ) + .details( "AFFILIATION:" + gar.getResponse().name() + + " FOR GROUP:" + gar.getGroupWACN() + "-" + + gar.getGroupSystemID() + "-" + + gar.getGroupID() + " ANNOUNCEMENT GROUP:" + + gar.getAnnouncementGroupID() ) + .from( gar.getSourceWACN() + "-" + + gar.getSourceSystemID() + "-" + + gar.getSourceID() ) + .to( gar.getTargetAddress() ) + .build(); + + mLastResponseEventID = gar.getTargetAddress(); + } break; case ROAMING_ADDRESS_UPDATE: RoamingAddressUpdateExtended raue = (RoamingAddressUpdateExtended)message; - + StringBuilder sb = new StringBuilder(); sb.append( "ROAMING ADDRESS STACK A:" ); sb.append( raue.getWACNA() + "-" + raue.getSystemIDA() ); @@ -666,27 +774,31 @@ public class P25ChannelState extends ChannelState sb.append( raue.getWACNG() + "-" + raue.getSystemIDG() ); } - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.RESPONSE ) + event = new P25CallEvent.Builder( CallEventType.RESPONSE ) .aliasList( mAliasList ) .details( sb.toString() ) .from( raue.getSourceID() ) .to( raue.getTargetAddress() ) - .build() ); + .build(); break; case STATUS_UPDATE: StatusUpdateExtended su = (StatusUpdateExtended)message; - - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( mAliasList ) - .details( "STATUS USER: " + su.getUserStatus() + - " UNIT: " + su.getUnitStatus() ) - .from( su.getSourceWACN() + "-" + - su.getSourceSystemID() + "-" + - su.getSourceID() ) - .to( su.getTargetAddress() ) - .build() ); + + if( mLastResponseEventID == null || !mLastResponseEventID + .contentEquals( su.getTargetAddress() ) ) + { + event = new P25CallEvent.Builder( CallEventType.RESPONSE ) + .aliasList( mAliasList ) + .details( "STATUS USER: " + su.getUserStatus() + + " UNIT: " + su.getUnitStatus() ) + .from( su.getSourceWACN() + "-" + + su.getSourceSystemID() + "-" + + su.getSourceID() ) + .to( su.getTargetAddress() ) + .build(); + + mLastResponseEventID = su.getTargetAddress(); + } break; case UNIT_REGISTRATION_RESPONSE: UnitRegistrationResponseExtended urr = @@ -698,11 +810,10 @@ public class P25ChannelState extends ChannelState System.currentTimeMillis() ); } - if( mLastRegistrationEventID != null && - !mLastRegistrationEventID.contentEquals( urr.getAssignedSourceAddress() ) ) + if( mLastRegistrationEventID == null || !mLastRegistrationEventID + .contentEquals( urr.getAssignedSourceAddress() ) ) { - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.REGISTER ) + event = new P25CallEvent.Builder( CallEventType.REGISTER ) .aliasList( mAliasList ) .details( "REGISTRATION:" + urr.getResponse().name() + " FOR EXTERNAL SYSTEM ADDRESS: " + @@ -711,14 +822,19 @@ public class P25ChannelState extends ChannelState urr.getSourceAddress() + " SOURCE ID: " + urr.getSourceID() ) .from( urr.getAssignedSourceAddress() ) - .build() ); + .build(); + + mLastRegistrationEventID = urr.getAssignedSourceAddress(); } - - mLastRegistrationEventID = urr.getAssignedSourceAddress(); break; default: break; } + + if( event != null ) + { + mCallEventModel.add( event ); + } } private void processRFSSStatus( RFSSStatusBroadcast message ) @@ -818,9 +934,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.DATA_CALL ) .aliasList( mAliasList ) .channel( gdcge.getTransmitChannel() ) - .details( "GROUP DATA CHANNEL GRANT EXTENDED" ) -// .details( ( gdcg.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gdcg.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gdcge.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gdcge.isEmergency() ? " EMERGENCY" : "") ) .frequency( gdcge.getDownlinkFrequency() ) .from( gdcge.getSourceAddress() ) .to( gdcge.getGroupAddress() ) @@ -845,9 +960,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.CALL ) .aliasList( mAliasList ) .channel( gvcge.getTransmitChannel() ) - .details( "GROUP_VOICE_CHANNEL_GRANT" ) -// .details( ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gvcg.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gvcge.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcge.isEmergency() ? " EMERGENCY" : "") ) .frequency( gvcge.getDownlinkFrequency() ) .from( gvcge.getSourceAddress() ) .to( gvcge.getGroupAddress() ) @@ -872,9 +986,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.DATA_CALL ) .aliasList( mAliasList ) .channel( idcge.getTransmitChannel() ) - .details( "INDIV DATA CHANNEL GRANT EXTENDED" ) -// .details( ( idcg.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( idcg.isEmergency() ? " EMERGENCY" : "") ) + .details( ( idcge.isEncrypted() ? "ENCRYPTED" : "" ) + + ( idcge.isEmergency() ? " EMERGENCY" : "") ) .frequency( idcge.getDownlinkFrequency() ) .from( idcge.getSourceWACN() + "-" + idcge.getSourceSystemID() + "-" + @@ -927,9 +1040,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.CALL ) .aliasList( mAliasList ) .channel( uuvcge.getTransmitChannel() ) - .details( "UNIT_TO_UNIT_VOICE_CHANNEL_GRANT" ) -// .details( ( uuvcge.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( uuvcge.isEmergency() ? " EMERGENCY" : "") ) + .details( ( uuvcge.isEncrypted() ? "ENCRYPTED" : "" ) + + ( uuvcge.isEmergency() ? " EMERGENCY" : "") ) .frequency( uuvcge.getDownlinkFrequency() ) .from( uuvcge.getSourceWACN() + "-" + uuvcge.getSourceSystemID() + "-" + @@ -958,9 +1070,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.CALL ) .aliasList( mAliasList ) .channel( uuvcgue.getTransmitChannel() ) - .details( "UNIT_TO_UNIT_VOICE_CHANNEL_GRANT" ) - // .details( ( uuvcge.isEncrypted() ? "ENCRYPTED" : "" ) + - // ( uuvcge.isEmergency() ? " EMERGENCY" : "") ) + .details( ( uuvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + + ( uuvcgue.isEmergency() ? " EMERGENCY" : "") ) .frequency( uuvcgue.getDownlinkFrequency() ) .from( uuvcgue.getSourceWACN() + "-" + uuvcgue.getSourceSystemID() + "-" + @@ -1011,9 +1122,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.DATA_CALL ) .aliasList( mAliasList ) .channel( gdca.getChannel1() ) - .details( "GROUP DATA CHANNEL ANNOUNCEMENT" ) -// .details( ( gdca.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gdca.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gdca.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gdca.isEmergency() ? " EMERGENCY" : "") ) .frequency( gdca.getDownlinkFrequency1() ) .to( gdca.getGroupAddress1() ) .build(); @@ -1035,9 +1145,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.DATA_CALL ) .aliasList( mAliasList ) .channel( gdca.getChannel2() ) - .details( "GROUP DATA CHANNEL ANNOUNCEMENT" ) -// .details( ( gdca.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gdca.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gdca.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gdca.isEmergency() ? " EMERGENCY" : "") ) .frequency( gdca.getDownlinkFrequency2() ) .to( gdca.getGroupAddress2() ) .build(); @@ -1062,9 +1171,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.DATA_CALL ) .aliasList( mAliasList ) .channel( gdcae.getTransmitChannel() ) - .details( "GROUP DATA CHANNEL ANNOUNCEMENT EXPLICIT" ) -// .details( ( gdca.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gdca.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gdcae.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gdcae.isEmergency() ? " EMERGENCY" : "") ) .frequency( gdcae.getDownlinkFrequency() ) .to( gdcae.getGroupAddress() ) .build(); @@ -1087,9 +1195,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.DATA_CALL ) .aliasList( mAliasList ) .channel( gdcg.getChannel() ) - .details( "GROUP DATA CHANNEL GRANT" ) -// .details( ( gdcg.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gdcg.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gdcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gdcg.isEmergency() ? " EMERGENCY" : "") ) .frequency( gdcg.getDownlinkFrequency() ) .from( gdcg.getSourceAddress() ) .to( gdcg.getGroupAddress() ) @@ -1113,9 +1220,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.CALL ) .aliasList( mAliasList ) .channel( gvcg.getChannel() ) - .details( "GROUP_VOICE_CHANNEL_GRANT" ) -// .details( ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gvcg.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcg.isEmergency() ? " EMERGENCY" : "") ) .frequency( gvcg.getDownlinkFrequency() ) .from( gvcg.getSourceAddress() ) .to( gvcg.getGroupAddress() ) @@ -1140,9 +1246,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.CALL ) .aliasList( mAliasList ) .channel( gvcgu.getChannel1() ) - .details( "1-GROUP_VOICE_CHANNEL_GRANT_UPDATE" ) -// .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) .frequency( gvcgu.getDownlinkFrequency1() ) .to( gvcgu.getGroupAddress1() ) .build(); @@ -1164,9 +1269,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.CALL ) .aliasList( mAliasList ) .channel( gvcgu.getChannel2() ) - .details( "2-GROUP_VOICE_CHANNEL_GRANT_UPDATE" ) -// .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) .frequency( gvcgu.getDownlinkFrequency2() ) .to( gvcgu.getGroupAddress2() ) .build(); @@ -1186,9 +1290,8 @@ public class P25ChannelState extends ChannelState .aliasList( mAliasList ) .channel( gvcgue.getTransmitChannelIdentifier() + "-" + gvcgue.getTransmitChannelNumber() ) - .details( "GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT") -// .details( ( gvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( gvcgue.isEmergency() ? " EMERGENCY" : "") ) + .details( ( gvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + + ( gvcgue.isEmergency() ? " EMERGENCY" : "") ) .frequency( gvcgue.getDownlinkFrequency() ) .to( gvcgue.getGroupAddress() ) .build() ); @@ -1206,9 +1309,8 @@ public class P25ChannelState extends ChannelState new P25CallEvent.Builder( CallEventType.DATA_CALL ) .aliasList( mAliasList ) .channel( idcg.getChannel() ) - .details( "INDIV DATA CHANNEL GRANT" ) -// .details( ( idcg.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( idcg.isEmergency() ? " EMERGENCY" : "") ) + .details( ( idcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( idcg.isEmergency() ? " EMERGENCY" : "") ) .frequency( idcg.getDownlinkFrequency() ) .from( idcg.getSourceAddress() ) .to( idcg.getTargetAddress() ) @@ -1222,18 +1324,25 @@ public class P25ChannelState extends ChannelState case SNDCP_DATA_CHANNEL_GRANT: SNDCPDataChannelGrant sdcg = (SNDCPDataChannelGrant)message; - CallEvent event = - new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( mAliasList ) - .channel( sdcg.getTransmitChannel() ) - .details( "SNDCP DATA NSAPI:" + sdcg.getNSAPI() ) - .frequency( sdcg.getDownlinkFrequency() ) - .to( sdcg.getTargetAddress() ) - .build(); + if( isActiveCall( sdcg.getTransmitChannel(), sdcg.getTargetAddress() ) ) + { + updateCall( sdcg.getTransmitChannel(), sdcg.getTargetAddress() ); + } + else + { + CallEvent event = + new P25CallEvent.Builder( CallEventType.DATA_CALL ) + .aliasList( mAliasList ) + .channel( sdcg.getTransmitChannel() ) + .details( "SNDCP DATA NSAPI:" + sdcg.getNSAPI() ) + .frequency( sdcg.getDownlinkFrequency() ) + .to( sdcg.getTargetAddress() ) + .build(); - mCallEventModel.add( event ); - - addCall( event ); + mCallEventModel.add( event ); + + addCall( event ); + } break; case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: TelephoneInterconnectVoiceChannelGrant tivcg = @@ -1276,9 +1385,8 @@ public class P25ChannelState extends ChannelState .aliasList( mAliasList ) .channel( tivcgu.getChannelIdentifier() + "-" + tivcgu.getChannelNumber() ) - .details( "TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE" ) -// .details( ( tivcgu.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( tivcgu.isEmergency() ? " EMERGENCY" : "") ) + .details( ( tivcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( tivcgu.isEmergency() ? " EMERGENCY" : "") ) .frequency( tivcgu.getDownlinkFrequency() ) .from( tivcgu.getAddress() ) .build(); @@ -1303,9 +1411,8 @@ public class P25ChannelState extends ChannelState .aliasList( mAliasList ) .channel( uuvcg.getChannelIdentifier() + "-" + uuvcg.getChannelNumber() ) - .details( "UNIT_TO_UNIT_VOICE_CHANNEL_GRANT" ) -// .details( ( uuvcg.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( uuvcg.isEmergency() ? " EMERGENCY" : "") ) + .details( ( uuvcg.isEncrypted() ? "ENCRYPTED" : "" ) + + ( uuvcg.isEmergency() ? " EMERGENCY" : "") ) .frequency( uuvcg.getDownlinkFrequency() ) .from( uuvcg.getSourceAddress() ) .to( uuvcg.getTargetAddress() ) @@ -1332,9 +1439,8 @@ public class P25ChannelState extends ChannelState .aliasList( mAliasList ) .channel( uuvcgu.getChannelIdentifier() + "-" + uuvcgu.getChannelNumber() ) - .details( "UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE" ) -// .details( ( uuvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + -// ( uuvcgu.isEmergency() ? " EMERGENCY" : "") ) + .details( ( uuvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + + ( uuvcgu.isEmergency() ? " EMERGENCY" : "") ) .frequency( uuvcgu.getDownlinkFrequency() ) .from( uuvcgu.getSourceAddress() ) .to( uuvcgu.getTargetAddress() ) @@ -1388,46 +1494,68 @@ public class P25ChannelState extends ChannelState */ private void processPage( TSBKMessage message ) { + CallEvent event = null; + switch( message.getOpcode() ) { case UNIT_TO_UNIT_ANSWER_REQUEST: UnitToUnitAnswerRequest utuar = (UnitToUnitAnswerRequest)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( mAliasList ) - .details( ( utuar.isEmergency() ? "EMERGENCY" : "" ) ) - .from( utuar.getSourceAddress() ) - .to( utuar.getTargetAddress() ) - .build() ); + if( mLastPageEventID == null || !mLastPageEventID + .contentEquals( utuar.getTargetAddress() ) ) + { + event = new P25CallEvent.Builder( CallEventType.PAGE ) + .aliasList( mAliasList ) + .details( ( utuar.isEmergency() ? "EMERGENCY" : "" ) ) + .from( utuar.getSourceAddress() ) + .to( utuar.getTargetAddress() ) + .build(); + + mLastPageEventID = utuar.getTargetAddress(); + } break; case SNDCP_DATA_PAGE_REQUEST: SNDCPDataPageRequest sdpr = (SNDCPDataPageRequest)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( mAliasList ) - .details( "SNDCP DATA DAC: " + - sdpr.getDataAccessControl() + - " NSAPI:" + sdpr.getNSAPI() ) - .to( sdpr.getTargetAddress() ) - .build() ); + if( mLastPageEventID == null || !mLastPageEventID + .contentEquals( sdpr.getTargetAddress() ) ) + { + event = new P25CallEvent.Builder( CallEventType.PAGE ) + .aliasList( mAliasList ) + .details( "SNDCP DATA DAC: " + + sdpr.getDataAccessControl() + + " NSAPI:" + sdpr.getNSAPI() ) + .to( sdpr.getTargetAddress() ) + .build(); + + mLastPageEventID = sdpr.getTargetAddress(); + } break; case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: TelephoneInterconnectAnswerRequest tiar = (TelephoneInterconnectAnswerRequest)message; - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.PAGE ) + if( mLastPageEventID == null || !mLastPageEventID + .contentEquals( tiar.getTargetAddress() ) ) + { + event = new P25CallEvent.Builder( CallEventType.PAGE ) .aliasList( mAliasList ) .details( ( "TELEPHONE INTERCONNECT" ) ) .from( tiar.getTelephoneNumber() ) .to( tiar.getTargetAddress() ) - .build() ); + .build(); + + mLastPageEventID = tiar.getTargetAddress(); + } break; default: break; } + + if( event != null ) + { + mCallEventModel.add( event ); + } } private void processPage( PDUMessage message ) @@ -1436,16 +1564,22 @@ public class P25ChannelState extends ChannelState { UnitToUnitAnswerRequestExplicit utuare = (UnitToUnitAnswerRequestExplicit)message; - - mCallEventModel.add( - new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( mAliasList ) - .details( ( utuare.isEmergency() ? "EMERGENCY" : "" ) ) - .from( utuare.getSourceID() ) - .to( utuare.getWACN() + "-" + - utuare.getSystemID() + "-" + - utuare.getTargetAddress() ) - .build() ); + + if( mLastPageEventID == null || !mLastPageEventID + .contentEquals( utuare.getTargetAddress() ) ) + { + mCallEventModel.add( + new P25CallEvent.Builder( CallEventType.PAGE ) + .aliasList( mAliasList ) + .details( ( utuare.isEmergency() ? "EMERGENCY" : "" ) ) + .from( utuare.getWACN() + "-" + + utuare.getSystemID() + "-" + + utuare.getSourceID() ) + .to( utuare.getTargetAddress() ) + .build() ); + + mLastPageEventID = utuare.getTargetAddress(); + } } } diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java index be1fb72d..5559743e 100644 --- a/src/decode/p25/P25Decoder.java +++ b/src/decode/p25/P25Decoder.java @@ -19,6 +19,7 @@ package decode.p25; import instrument.Instrumentable; import instrument.tap.Tap; +import instrument.tap.stream.DibitTap; import instrument.tap.stream.FloatTap; import java.util.ArrayList; @@ -36,9 +37,9 @@ import dsp.agc.RealAutomaticGainControl; import dsp.filter.ComplexFIRFilter; import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; -import dsp.fsk.P25MessageFramer; import dsp.fsk.C4FMSlicer; import dsp.fsk.C4FMSymbolFilter; +import dsp.fsk.P25MessageFramer; import dsp.nbfm.FMDiscriminator; public class P25Decoder extends Decoder implements Instrumentable @@ -49,6 +50,7 @@ public class P25Decoder extends Decoder implements Instrumentable private static final String INSTRUMENT_INPUT_TO_AGC = "Tap Point: Input to AGC"; private static final String INSTRUMENT_AGC_TO_SYMBOL_FILTER = "Tap Point: AGC to Symbol Filter"; private static final String INSTRUMENT_SYMBOL_FILTER_TO_SLICER = "Tap Point: Symbol Filter to Slicer"; + private static final String INSTRUMENT_SLICER_OUTPUT = "Tap Point: Slicer Output"; private List mAvailableTaps; private ComplexFIRFilter mBasebandFilter = new ComplexFIRFilter( @@ -125,6 +127,7 @@ public class P25Decoder extends Decoder implements Instrumentable mAvailableTaps.add( new FloatTap( INSTRUMENT_INPUT_TO_AGC, 0, 1.0f ) ); mAvailableTaps.add( new FloatTap( INSTRUMENT_AGC_TO_SYMBOL_FILTER, 0, 1.0f ) ); mAvailableTaps.add( new FloatTap( INSTRUMENT_SYMBOL_FILTER_TO_SLICER, 0, 0.1f ) ); + mAvailableTaps.add( new DibitTap( INSTRUMENT_SLICER_OUTPUT, 0, 0.1f ) ); mAvailableTaps.addAll( mSymbolFilter.getTaps() ); } @@ -155,6 +158,10 @@ public class P25Decoder extends Decoder implements Instrumentable mSymbolFilter.setListener( symbolSlicer ); symbolSlicer.setListener( mSlicer ); break; + case INSTRUMENT_SLICER_OUTPUT: + DibitTap slicer = (DibitTap)tap; + mSlicer.addListener( slicer ); + break; } } @@ -176,6 +183,10 @@ public class P25Decoder extends Decoder implements Instrumentable case INSTRUMENT_SYMBOL_FILTER_TO_SLICER: mSymbolFilter.setListener( mSlicer ); break; + case INSTRUMENT_SLICER_OUTPUT: + DibitTap slicerTap = (DibitTap)tap; + mSlicer.removeListener( slicerTap ); + break; } } diff --git a/src/decode/p25/message/hdu/HDUMessage.java b/src/decode/p25/message/hdu/HDUMessage.java new file mode 100644 index 00000000..b2ab952b --- /dev/null +++ b/src/decode/p25/message/hdu/HDUMessage.java @@ -0,0 +1,44 @@ +package decode.p25.message.hdu; + +import alias.AliasList; +import bits.BitSetBuffer; +import crc.CRC; +import decode.p25.message.P25Message; +import decode.p25.reference.DataUnitID; + +public class HDUMessage extends P25Message +{ + public HDUMessage( BitSetBuffer message, DataUnitID duid, + AliasList aliasList ) + { + super( message, duid, aliasList ); + + /* NID CRC is checked in the message framer, thus a constructed message + * means it passed the CRC */ + mCRC = new CRC[ 2 ]; + mCRC[ 0 ] = CRC.PASSED; + } + + @Override + public String getMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "NAC:" ); + sb.append( getNAC() ); + sb.append( " " ); + sb.append( getDUID().getLabel() ); + sb.append( " " ); + + sb.append( mMessage.toString() ); + + return sb.toString(); + } + + /* Temporary override */ + public boolean isValid() + { + return true; + } + +} diff --git a/src/decode/p25/message/ldu/LDU1Message.java b/src/decode/p25/message/ldu/LDU1Message.java new file mode 100644 index 00000000..d5c74ec5 --- /dev/null +++ b/src/decode/p25/message/ldu/LDU1Message.java @@ -0,0 +1,44 @@ +package decode.p25.message.ldu; + +import alias.AliasList; +import bits.BitSetBuffer; +import crc.CRC; +import decode.p25.message.P25Message; +import decode.p25.reference.DataUnitID; + +public class LDU1Message extends P25Message +{ + public LDU1Message( BitSetBuffer message, DataUnitID duid, + AliasList aliasList ) + { + super( message, duid, aliasList ); + + /* NID CRC is checked in the message framer, thus a constructed message + * means it passed the CRC */ + mCRC = new CRC[ 2 ]; + mCRC[ 0 ] = CRC.PASSED; + } + + @Override + public String getMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "NAC:" ); + sb.append( getNAC() ); + sb.append( " " ); + sb.append( getDUID().getLabel() ); + sb.append( " " ); + + sb.append( mMessage.toString() ); + + return sb.toString(); + } + + /* Temporary override */ + public boolean isValid() + { + return true; + } + +} diff --git a/src/decode/p25/message/ldu/LDU2Message.java b/src/decode/p25/message/ldu/LDU2Message.java new file mode 100644 index 00000000..40a05f69 --- /dev/null +++ b/src/decode/p25/message/ldu/LDU2Message.java @@ -0,0 +1,44 @@ +package decode.p25.message.ldu; + +import alias.AliasList; +import bits.BitSetBuffer; +import crc.CRC; +import decode.p25.message.P25Message; +import decode.p25.reference.DataUnitID; + +public class LDU2Message extends P25Message +{ + public LDU2Message( BitSetBuffer message, DataUnitID duid, + AliasList aliasList ) + { + super( message, duid, aliasList ); + + /* NID CRC is checked in the message framer, thus a constructed message + * means it passed the CRC */ + mCRC = new CRC[ 2 ]; + mCRC[ 0 ] = CRC.PASSED; + } + + @Override + public String getMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "NAC:" ); + sb.append( getNAC() ); + sb.append( " " ); + sb.append( getDUID().getLabel() ); + sb.append( " " ); + + sb.append( mMessage.toString() ); + + return sb.toString(); + } + + /* Temporary override */ + public boolean isValid() + { + return true; + } + +} diff --git a/src/decode/p25/message/pdu/UnitToUnitChannelGrantExtended.java b/src/decode/p25/message/pdu/UnitToUnitChannelGrantExtended.java index aa3ae249..0b241b2a 100644 --- a/src/decode/p25/message/pdu/UnitToUnitChannelGrantExtended.java +++ b/src/decode/p25/message/pdu/UnitToUnitChannelGrantExtended.java @@ -92,7 +92,7 @@ public abstract class UnitToUnitChannelGrantExtended extends PDUMessage return mMessage.get( EMERGENCY_FLAG ); } - public boolean isEncryptedChannel() + public boolean isEncrypted() { return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); } diff --git a/src/decode/p25/message/pdu/osp/data/GroupDataChannelGrantExtended.java b/src/decode/p25/message/pdu/osp/data/GroupDataChannelGrantExtended.java index 33b41c9c..54e7cde7 100644 --- a/src/decode/p25/message/pdu/osp/data/GroupDataChannelGrantExtended.java +++ b/src/decode/p25/message/pdu/osp/data/GroupDataChannelGrantExtended.java @@ -86,7 +86,7 @@ public class GroupDataChannelGrantExtended extends PDUMessage return mMessage.get( EMERGENCY_FLAG ); } - public boolean isEncryptedChannel() + public boolean isEncrypted() { return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); } diff --git a/src/decode/p25/message/pdu/osp/voice/GroupVoiceChannelGrantExplicit.java b/src/decode/p25/message/pdu/osp/voice/GroupVoiceChannelGrantExplicit.java index 4b4decec..f7ae8c92 100644 --- a/src/decode/p25/message/pdu/osp/voice/GroupVoiceChannelGrantExplicit.java +++ b/src/decode/p25/message/pdu/osp/voice/GroupVoiceChannelGrantExplicit.java @@ -84,7 +84,7 @@ public class GroupVoiceChannelGrantExplicit extends PDUMessage return mMessage.get( EMERGENCY_FLAG ); } - public boolean isEncryptedChannel() + public boolean isEncrypted() { return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); } diff --git a/src/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantUpdateExtended.java b/src/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantUpdateExtended.java index 03a94269..1460a96d 100644 --- a/src/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantUpdateExtended.java +++ b/src/decode/p25/message/pdu/osp/voice/UnitToUnitVoiceChannelGrantUpdateExtended.java @@ -99,7 +99,7 @@ public class UnitToUnitVoiceChannelGrantUpdateExtended extends PDUMessage return mMessage.get( EMERGENCY_FLAG ); } - public boolean isEncryptedChannel() + public boolean isEncrypted() { return mMessage.get( ENCRYPTED_CHANNEL_FLAG ); } diff --git a/src/decode/p25/message/tdu/TDULCMessage.java b/src/decode/p25/message/tdu/TDULCMessage.java new file mode 100644 index 00000000..0c35ae84 --- /dev/null +++ b/src/decode/p25/message/tdu/TDULCMessage.java @@ -0,0 +1,44 @@ +package decode.p25.message.tdu; + +import alias.AliasList; +import bits.BitSetBuffer; +import crc.CRC; +import decode.p25.message.P25Message; +import decode.p25.reference.DataUnitID; + +public class TDULCMessage extends P25Message +{ + public TDULCMessage( BitSetBuffer message, DataUnitID duid, + AliasList aliasList ) + { + super( message, duid, aliasList ); + + /* NID CRC is checked in the message framer, thus a constructed message + * means it passed the CRC */ + mCRC = new CRC[ 2 ]; + mCRC[ 0 ] = CRC.PASSED; + } + + @Override + public String getMessage() + { + StringBuilder sb = new StringBuilder(); + + sb.append( "NAC:" ); + sb.append( getNAC() ); + sb.append( " " ); + sb.append( getDUID().getLabel() ); + sb.append( " " ); + + sb.append( mMessage.toString() ); + + return sb.toString(); + } + + /* Temporary override */ + public boolean isValid() + { + return true; + } + +} diff --git a/src/decode/p25/message/tsbk/TSBKMessageFactory.java b/src/decode/p25/message/tsbk/TSBKMessageFactory.java index 8d978088..435427b6 100644 --- a/src/decode/p25/message/tsbk/TSBKMessageFactory.java +++ b/src/decode/p25/message/tsbk/TSBKMessageFactory.java @@ -20,6 +20,7 @@ import decode.p25.message.tsbk.osp.control.LocationRegistrationResponse; import decode.p25.message.tsbk.osp.control.MessageUpdate; import decode.p25.message.tsbk.osp.control.NetworkStatusBroadcast; import decode.p25.message.tsbk.osp.control.ProtectionParameterUpdate; +import decode.p25.message.tsbk.osp.control.QueuedResponse; import decode.p25.message.tsbk.osp.control.RFSSStatusBroadcast; import decode.p25.message.tsbk.osp.control.RadioUnitMonitorCommand; import decode.p25.message.tsbk.osp.control.RoamingAddressCommand; @@ -125,6 +126,8 @@ public class TSBKMessageFactory case NETWORK_STATUS_BROADCAST: return new NetworkStatusBroadcast( message, duid, aliasList ); + case QUEUED_RESPONSE: + return new QueuedResponse( message, duid, aliasList ); case PROTECTION_PARAMETER_UPDATE: return new ProtectionParameterUpdate( message, duid, aliasList ); diff --git a/src/decode/p25/message/tsbk/osp/control/AuthenticationCommand.java b/src/decode/p25/message/tsbk/osp/control/AuthenticationCommand.java index 6ebb2f13..c220ca10 100644 --- a/src/decode/p25/message/tsbk/osp/control/AuthenticationCommand.java +++ b/src/decode/p25/message/tsbk/osp/control/AuthenticationCommand.java @@ -55,6 +55,19 @@ public class AuthenticationCommand extends TSBKMessage { return mMessage.getHex( TARGET_ID, 6 ); } + + public String getFullTargetID() + { + StringBuilder sb = new StringBuilder(); + + sb.append( getWACN() ); + sb.append( "-" ); + sb.append( getSystemID() ); + sb.append( "-" ); + sb.append( getTargetID() ); + + return sb.toString(); + } @Override public String getToID() diff --git a/src/decode/p25/reference/DataUnitID.java b/src/decode/p25/reference/DataUnitID.java index e39f57d2..54e1ce0a 100644 --- a/src/decode/p25/reference/DataUnitID.java +++ b/src/decode/p25/reference/DataUnitID.java @@ -18,7 +18,7 @@ public enum DataUnitID PDU1 ( 12, 356, false, "PDU1 ", "Packet Data Unit" ), PDU2 ( 12, 452, false, "PDU2 ", "Packet Data Unit" ), PDU3 ( 12, 548, false, "PDU3 ", "Packet Data Unit" ), - TDULC( 15, 372, false, "TDULC", "Terminator Data Unit With Link Control" ), + TDULC( 15, 352, false, "TDULC", "Terminator Data Unit With Link Control" ), UNKN ( -1, 0, false, "UNKWN", "Unknown" ); private int mValue; diff --git a/src/dsp/fsk/P25MessageFramer.java b/src/dsp/fsk/P25MessageFramer.java index 3629a894..7d64c6e1 100644 --- a/src/dsp/fsk/P25MessageFramer.java +++ b/src/dsp/fsk/P25MessageFramer.java @@ -17,8 +17,12 @@ import crc.CRCP25; import decode.p25.P25Interleave; import decode.p25.TrellisHalfRate; import decode.p25.message.P25Message; +import decode.p25.message.hdu.HDUMessage; +import decode.p25.message.ldu.LDU1Message; +import decode.p25.message.ldu.LDU2Message; import decode.p25.message.pdu.PDUMessage; import decode.p25.message.pdu.PDUMessageFactory; +import decode.p25.message.tdu.TDULCMessage; import decode.p25.message.tdu.TDUMessage; import decode.p25.message.tsbk.TSBKMessage; import decode.p25.message.tsbk.TSBKMessageFactory; @@ -246,15 +250,15 @@ public class P25MessageFramer implements Listener break; case HDU: mComplete = true; - dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) ); + dispatch( new HDUMessage( mMessage.copy(), mDUID, mAliasList ) ); break; case LDU1: mComplete = true; - dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) ); + dispatch( new LDU1Message( mMessage.copy(), mDUID, mAliasList ) ); break; case LDU2: mComplete = true; - dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) ); + dispatch( new LDU2Message( mMessage.copy(), mDUID, mAliasList ) ); break; case PDU0: /* Remove interleaving */ @@ -368,7 +372,7 @@ public class P25MessageFramer implements Listener mComplete = true; break; case TDULC: - dispatch( new P25Message( mMessage.copy(), mDUID, mAliasList ) ); + dispatch( new TDULCMessage( mMessage.copy(), mDUID, mAliasList ) ); mComplete = true; break; case TSBK1: diff --git a/src/instrument/gui/DecoderViewFrame.java b/src/instrument/gui/DecoderViewFrame.java index 5311c161..939f8788 100644 --- a/src/instrument/gui/DecoderViewFrame.java +++ b/src/instrument/gui/DecoderViewFrame.java @@ -22,6 +22,8 @@ import instrument.tap.Tap; import instrument.tap.TapViewPanel; import instrument.tap.stream.BinaryTap; import instrument.tap.stream.BinaryTapViewPanel; +import instrument.tap.stream.DibitTap; +import instrument.tap.stream.DibitTapViewPanel; import instrument.tap.stream.FloatTap; import instrument.tap.stream.FloatTapViewPanel; import instrument.tap.stream.SymbolEventTap; @@ -166,6 +168,9 @@ public class DecoderViewFrame extends JInternalFrame break; case STREAM_COMPLEX: break; + case STREAM_DIBIT: + panel = new DibitTapViewPanel( (DibitTap)tap ); + break; case STREAM_FLOAT: panel = new FloatTapViewPanel( (FloatTap)tap ); break; diff --git a/src/instrument/tap/TapType.java b/src/instrument/tap/TapType.java index b9bb9ed3..c6cd5eb7 100644 --- a/src/instrument/tap/TapType.java +++ b/src/instrument/tap/TapType.java @@ -22,6 +22,7 @@ public enum TapType EVENT_SYNC_DETECT, STREAM_BINARY, STREAM_COMPLEX, + STREAM_DIBIT, STREAM_FLOAT, STREAM_SYMBOL; } diff --git a/src/instrument/tap/stream/DibitTap.java b/src/instrument/tap/stream/DibitTap.java new file mode 100644 index 00000000..1b1f8f02 --- /dev/null +++ b/src/instrument/tap/stream/DibitTap.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 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 + ******************************************************************************/ +package instrument.tap.stream; + +import instrument.tap.TapListener; +import instrument.tap.TapType; +import sample.Listener; +import dsp.fsk.Dibit; + +public class DibitTap extends StreamTap implements Listener + +{ + private Listener mListener; + + public DibitTap( String name, + int delay, + float sampleRateRatio ) + { + super( TapType.STREAM_DIBIT, name, delay, sampleRateRatio ); + } + + @Override + public void receive( Dibit dibit ) + { + if( mListener != null ) + { + mListener.receive( dibit ); + } + + for( TapListener listener: mListeners ) + { + listener.receive( dibit ); + } + } + + public void setListener( Listener listener ) + { + mListener = listener; + } + + public void removeListener( Listener listener ) + { + mListener = null; + } +} diff --git a/src/instrument/tap/stream/DibitTapViewPanel.java b/src/instrument/tap/stream/DibitTapViewPanel.java new file mode 100644 index 00000000..5c496cfd --- /dev/null +++ b/src/instrument/tap/stream/DibitTapViewPanel.java @@ -0,0 +1,190 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 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 + ******************************************************************************/ +package instrument.tap.stream; + +import instrument.gui.SampleModel; +import instrument.tap.Tap; +import instrument.tap.TapViewPanel; + +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; +import java.util.List; +import java.util.Observable; + +import dsp.fsk.Dibit; + +public class DibitTapViewPanel extends TapViewPanel +{ + private static final long serialVersionUID = 1L; + + private Tap mTap; + private List mSamples; + private int mSampleCount; + private int mOffset = 0; + + public DibitTapViewPanel( DibitTap tap ) + { + super( new SampleModel(), tap.getName() ); + + mTap = tap; + mTap.addListener( getModel() ); + + getModel().setSampleCount( + (int)( getModel().getSampleCount() * tap.getSampleRateRatio() ) ); + + getModel().setDelay( tap.getDelay() ); + } + + @Override + public void update( Observable arg0, Object arg1 ) + { + mSamples = (List)getModel().getSamples(); + mSampleCount = getModel().getSampleCount(); + repaint(); + } + + public void paintComponent( Graphics g ) + { + super.paintComponent( g ); + + Graphics2D g2 = (Graphics2D)g; + + int height = getHeight(); + int width = getWidth(); + + g2.setColor( Color.DARK_GRAY ); + + float middle = (float)height / 2.0f; + + g2.drawLine( 0, (int)middle, width, (int)middle ); + + if( mSamples != null && mSamples.size() > 0 ) + { + g2.setColor( getForeground() ); + + g2.drawString( mLabel, 5 , 15 ); + + for( int x = 1; x < mSamples.size(); x++ ) + { + paintDibit( g, x, mSamples.get( x ) ); + } + } + + g2.dispose(); + } + + /** + * Paints a polyline representing a boolean value with a 0 or 1 label in + * the middle of it + * + * @param graphics + * @param index - index of the sample int the sample buffer + * @param value - bit value, 0 or 1 + */ + private void paintDibit( Graphics graphics, int index, Dibit value ) + { + Graphics2D g2 = (Graphics2D)graphics; + + Path2D.Float polyline = + new Path2D.Float( Path2D.Float.WIND_EVEN_ODD, mSamples.size() ); + + float middleY = ( (float)getHeight() / 2.0f ); + + float indexWidth = (float)( getWidth() ) / (float)mSampleCount; + + float startX = (float)index * indexWidth; + float stopX = (float)(index + 1 ) * indexWidth; + float fivePercent = indexWidth * .05f; + float leftX = startX + fivePercent; + float rightX = stopX - fivePercent; + + /* Start */ + polyline.moveTo( startX, middleY ); + + /* Left horizonal segment */ + polyline.lineTo( leftX, middleY ); + + float y = middleY; + + switch( value ) + { + case D01_PLUS_3: + y = middleY - ( middleY * 0.75f); + break; + case D00_PLUS_1: + y = middleY - ( middleY * 0.25f); + break; + case D10_MINUS_1: + y = middleY + ( middleY * 0.25f); + break; + case D11_MINUS_3: + y = middleY + ( middleY * 0.75f); + break; + } + + /* Left vertical segment */ + polyline.lineTo( leftX, y ); + + /* Middle horizontal segment */ + polyline.lineTo( rightX, y ); + + /* Right vertical segment */ + polyline.lineTo( rightX, middleY ); + + /* Right horizontal segment */ + polyline.lineTo( stopX, middleY ); + + g2.draw( polyline ); + + /* Draw the bit value label */ + String bit = null; + + switch( value ) + { + case D00_PLUS_1: + bit = "+1"; + break; + case D01_PLUS_3: + bit = "+3"; + break; + case D10_MINUS_1: + bit = "-1"; + break; + case D11_MINUS_3: + bit = "-3"; + break; + } + + FontMetrics fontMetrics = graphics.getFontMetrics( this.getFont() ); + + Rectangle2D label = fontMetrics.getStringBounds( bit, graphics ); + + float offsetX = (float)label.getWidth() / 2.0f; + float offsetY = (float)label.getWidth() / 2.0f; + + float middleX = startX + ( indexWidth / 2.0f ); + + graphics.drawString( bit, + (int)( middleX - offsetX ), + (int)( middleY - offsetY ) ); + } +}