From 6bfe98dae6b6b78cfd6a929c1d8a3c23f142b34e Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 8 Jan 2017 07:54:48 -0500 Subject: [PATCH] #78 patch group alias and call event management. Adds new PatchGroupAlias which is a temporary alias group that is added to the alias list by the decoder state. The P25 decoder state maintains a mapping of patch group identifier to patched talkgroups and creates the patch group alias. This allows audio recording and audio streaming of patched talkgroups, according to the aliases corresponding to the group of patched aliases contained in the patch group alias. Event logging is now pared down to only logging events when the internal patch group mapping is updated. --- src/alias/Alias.java | 2 +- src/alias/PatchGroupAlias.java | 214 + src/alias/id/talkgroup/TalkgroupID.java | 112 +- src/module/decode/p25/P25DecoderState.java | 6852 +++++++++-------- .../p25/message/tsbk/motorola/PatchGroup.java | 47 +- 5 files changed, 3811 insertions(+), 3416 deletions(-) create mode 100644 src/alias/PatchGroupAlias.java diff --git a/src/alias/Alias.java b/src/alias/Alias.java index 3770b014..fd9ca32a 100644 --- a/src/alias/Alias.java +++ b/src/alias/Alias.java @@ -52,7 +52,7 @@ public class Alias public Alias() { } - + public Alias( String name ) { mName = name; diff --git a/src/alias/PatchGroupAlias.java b/src/alias/PatchGroupAlias.java new file mode 100644 index 00000000..6d5ddaa1 --- /dev/null +++ b/src/alias/PatchGroupAlias.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * sdrtrunk + * Copyright (C) 2014-2017 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 alias; + +import alias.action.AliasAction; +import alias.id.AliasID; +import alias.id.AliasIDType; +import alias.id.broadcast.BroadcastChannel; +import alias.id.priority.Priority; +import alias.id.talkgroup.TalkgroupID; + +import java.util.ArrayList; +import java.util.List; + +public class PatchGroupAlias extends Alias +{ + private List mPatchedAliases = new ArrayList<>(); + + /** + * Patch group alias is a single alias for a group of aliases that are temporarily patched together. This will + * normally be used for patched talkgroups, but could be used with any type of alias identifiers that can be + * temporarily joined to form a patch group. + * + * Alias properties such as recordable, streamable and call priority are an aggregation from the set of patched + * aliases contained in this patch group alias and where applicable, return the highest value/priority from the + * group of patched aliases. + */ + public PatchGroupAlias() + { + } + + /** + * List of patched aliases contained in this patch group + */ + public List getPatchedAliases() + { + return mPatchedAliases; + } + + /** + * Removes/clears all patched aliases from this patch alias group + */ + public void clearPatchedAliases() + { + mPatchedAliases.clear(); + } + + /** + * Adds the patched alias to this patch alias group + */ + public void addPatchedAlias(Alias alias) + { + mPatchedAliases.add(alias); + } + + /** + * Removes the patched alias from this patch alias group + */ + public void removePatchedAlias(Alias alias) + { + mPatchedAliases.remove(alias); + } + + @Override + public String getName() + { + if(getId().size() > 0) + { + for(AliasID id: getId()) + { + if(id.getType() == AliasIDType.TALKGROUP) + { + return "PATCH:" + ((TalkgroupID)id).getTalkgroup(); + } + } + } + + return "PATCH"; + } + + @Override + public List getAction() + { + List aliasActions = new ArrayList<>(); + + for(Alias alias: mPatchedAliases) + { + aliasActions.addAll(alias.getAction()); + } + + return aliasActions; + } + + @Override + public boolean hasActions() + { + for(Alias alias: mPatchedAliases) + { + if(alias.hasActions()) + { + return true; + } + } + + return false; + } + + @Override + public int getCallPriority() + { + int highestPriority = Priority.DEFAULT_PRIORITY; + + for(Alias alias: mPatchedAliases) + { + if(alias.getCallPriority() < highestPriority) + { + highestPriority = alias.getCallPriority(); + } + } + + return highestPriority; + } + + @Override + public boolean hasCallPriority() + { + for(Alias alias: mPatchedAliases) + { + if(alias.hasCallPriority()) + { + return true; + } + } + + return false; + } + + @Override + public boolean isRecordable() + { + for(Alias alias: mPatchedAliases) + { + if(alias.isRecordable()) + { + return true; + } + } + + return false; + } + + @Override + public boolean isStreamable() + { + for(Alias alias: mPatchedAliases) + { + if(alias.isStreamable()) + { + return true; + } + } + + return false; + } + + @Override + public List getBroadcastChannels() + { + List broadcastChannels = new ArrayList<>(); + + for(Alias alias: mPatchedAliases) + { + for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels()) + { + if(!broadcastChannels.contains(broadcastChannel)) + { + broadcastChannels.add(broadcastChannel); + } + } + } + + return broadcastChannels; + } + + @Override + public boolean hasBroadcastChannel(String channel) + { + for(Alias alias: mPatchedAliases) + { + if(alias.hasBroadcastChannel(channel)) + { + return true; + } + } + + return false; + } +} diff --git a/src/alias/id/talkgroup/TalkgroupID.java b/src/alias/id/talkgroup/TalkgroupID.java index fe7b3ec3..cb2e8097 100644 --- a/src/alias/id/talkgroup/TalkgroupID.java +++ b/src/alias/id/talkgroup/TalkgroupID.java @@ -1,83 +1,89 @@ /******************************************************************************* * 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 alias.id.talkgroup; -import javax.xml.bind.annotation.XmlAttribute; - import alias.id.AliasID; import alias.id.AliasIDType; +import javax.xml.bind.annotation.XmlAttribute; + public class TalkgroupID extends AliasID { - private String mTalkgroup; - - public TalkgroupID() - { - } + private String mTalkgroup; - @XmlAttribute - public String getTalkgroup() - { - return mTalkgroup; - } - - public void setTalkgroup( String talkgroup ) - { - mTalkgroup = talkgroup; - } - - @Override - public boolean isValid() - { - return mTalkgroup != null; - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - - sb.append( "Talkgroup: " + mTalkgroup ); - - return sb.toString(); - } - - @Override - public boolean matches( AliasID id ) + public TalkgroupID() { - boolean retVal = false; - - if( id instanceof TalkgroupID ) - { - TalkgroupID tgid = (TalkgroupID)id; - - //Create a pattern - replace * wildcards with regex single-char wildcard - String pattern = mTalkgroup.replace( "*", ".?" ); - - retVal = tgid.getTalkgroup().matches( pattern ); - } - - return retVal; + //No arg JAXB constructor } - @Override + public TalkgroupID(String talkgroup) + { + mTalkgroup = talkgroup; + } + + @XmlAttribute + public String getTalkgroup() + { + return mTalkgroup; + } + + public void setTalkgroup(String talkgroup) + { + mTalkgroup = talkgroup; + } + + @Override + public boolean isValid() + { + return mTalkgroup != null; + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("Talkgroup: " + mTalkgroup); + + return sb.toString(); + } + + @Override + public boolean matches(AliasID id) + { + boolean retVal = false; + + if(id instanceof TalkgroupID) + { + TalkgroupID tgid = (TalkgroupID) id; + + //Create a pattern - replace * wildcards with regex single-char wildcard + String pattern = mTalkgroup.replace("*", ".?"); + + retVal = tgid.getTalkgroup().matches(pattern); + } + + return retVal; + } + + @Override public AliasIDType getType() { - return AliasIDType.TALKGROUP; + return AliasIDType.TALKGROUP; } } diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java index b73bef11..c597f598 100644 --- a/src/module/decode/p25/P25DecoderState.java +++ b/src/module/decode/p25/P25DecoderState.java @@ -1,17 +1,17 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014,2015 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 ******************************************************************************/ @@ -19,6 +19,8 @@ package module.decode.p25; import alias.Alias; import alias.AliasList; +import alias.PatchGroupAlias; +import alias.id.talkgroup.TalkgroupID; import audio.metadata.Metadata; import audio.metadata.MetadataType; import controller.channel.Channel.ChannelType; @@ -143,812 +145,813 @@ import java.util.concurrent.ScheduledExecutorService; public class P25DecoderState extends DecoderState { - private final static Logger mLog = LoggerFactory.getLogger( P25DecoderState.class ); - private static final DecimalFormat mFrequencyFormatter = - new DecimalFormat( "0.000000" ); + private final static Logger mLog = LoggerFactory.getLogger(P25DecoderState.class); + private static final DecimalFormat mFrequencyFormatter = + new DecimalFormat("0.000000"); - private module.decode.p25.message.tsbk.osp.control.NetworkStatusBroadcast mNetworkStatus; - private NetworkStatusBroadcastExtended mNetworkStatusExtended; - private ProtectionParameterBroadcast mProtectionParameterBroadcast; - private RFSSStatusBroadcast mRFSSStatusMessage; - private RFSSStatusBroadcastExtended mRFSSStatusMessageExtended; - private SNDCPDataChannelAnnouncementExplicit mSNDCPDataChannel; + private module.decode.p25.message.tsbk.osp.control.NetworkStatusBroadcast mNetworkStatus; + private NetworkStatusBroadcastExtended mNetworkStatusExtended; + private ProtectionParameterBroadcast mProtectionParameterBroadcast; + private RFSSStatusBroadcast mRFSSStatusMessage; + private RFSSStatusBroadcastExtended mRFSSStatusMessageExtended; + private SNDCPDataChannelAnnouncementExplicit mSNDCPDataChannel; - private Set mSecondaryControlChannels = - new TreeSet<>(); - - private Map mBands = new HashMap<>(); - private Map mRegistrations = new HashMap<>(); - private Map mNeighborMap = new HashMap<>(); + private Set mSecondaryControlChannels = + new TreeSet<>(); - private String mLastCommandEventID; - private String mLastPageEventID; - private String mLastQueryEventID; - private String mLastRegistrationEventID; - private String mLastResponseEventID; + private Map mBands = new HashMap<>(); + private Map mRegistrations = new HashMap<>(); + private Map mNeighborMap = new HashMap<>(); + private Map> mPatchGroupMap = new HashMap<>(); - private String mNAC; - private String mSystem; - private String mSite; - private String mSiteAlias; - private String mFromTalkgroup; - private Alias mFromAlias; - private String mToTalkgroup; - private Alias mToAlias; - private String mCurrentChannel = "CURRENT"; - private long mCurrentChannelFrequency = 0; + private String mLastCommandEventID; + private String mLastPageEventID; + private String mLastQueryEventID; + private String mLastRegistrationEventID; + private String mLastResponseEventID; - private ChannelType mChannelType; - private Modulation mModulation; - private boolean mIgnoreDataCalls; - private boolean mControlChannelShutdownLogged; + private String mNAC; + private String mSystem; + private String mSite; + private String mSiteAlias; + private String mFromTalkgroup; + private Alias mFromAlias; + private String mToTalkgroup; + private Alias mToAlias; + private String mCurrentChannel = "CURRENT"; + private long mCurrentChannelFrequency = 0; - private P25CallEvent mCurrentCallEvent; - private List mCallDetectTalkgroups = new ArrayList<>(); - private Map mChannelCallMap = new HashMap<>(); - - public P25DecoderState( AliasList aliasList, - ChannelType channelType, - Modulation modulation, - boolean ignoreDataCalls ) - { - super( aliasList ); - mChannelType = channelType; - mModulation = modulation; - mIgnoreDataCalls = ignoreDataCalls; - } - - public Modulation getModulation() - { - return mModulation; - } - - @Override - public DecoderType getDecoderType() - { - return DecoderType.P25_PHASE1; - } + private ChannelType mChannelType; + private Modulation mModulation; + private boolean mIgnoreDataCalls; + private boolean mControlChannelShutdownLogged; - @Override - public void stop() - { - } + private P25CallEvent mCurrentCallEvent; + private List mCallDetectTalkgroups = new ArrayList<>(); + private Map mChannelCallMap = new HashMap<>(); - /** - * Performs a full reset to prepare this object for reuse on a new channel - */ - @Override - public void reset() - { - resetState(); - - updateNAC( null ); - updateSite( null ); - updateSystem( null ); - } + public P25DecoderState(AliasList aliasList, + ChannelType channelType, + Modulation modulation, + boolean ignoreDataCalls) + { + super(aliasList); + mChannelType = channelType; + mModulation = modulation; + mIgnoreDataCalls = ignoreDataCalls; + } - /** - * Resets any temporal state details - */ - private void resetState() - { - mFromTalkgroup = null; - broadcast( ChangedAttribute.FROM_TALKGROUP ); - - mFromAlias = null; - broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS ); - - mToTalkgroup = null; - broadcast( ChangedAttribute.TO_TALKGROUP ); - - mToAlias = null; - broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); + public Modulation getModulation() + { + return mModulation; + } - mCallDetectTalkgroups.clear(); - - if( mCurrentCallEvent != null ) - { - mCurrentCallEvent.end(); - } - - mCurrentCallEvent = null; - } + @Override + public DecoderType getDecoderType() + { + return DecoderType.P25_PHASE1; + } - /** - * Performs any initialization operations to prepare for use - */ - @Override - public void init() - { - } + @Override + public void stop() + { + } - /** - * Indicates if there a call event exists for the specified channel and - * from and to talkgroup identifiers. - * - * @param channel - channel number - * @param from user id - * @param to talkgroup or user id - * @return true if there is a call event - */ - public boolean hasCallEvent( String channel, String from, String to ) - { - boolean hasEvent = false; - - if( mChannelCallMap.containsKey( channel ) ) - { - P25CallEvent event = mChannelCallMap.get( channel ); - - if( to != null && - event.getToID() != null && - to.contentEquals( event.getToID() ) ) - { - if( from != null ) - { - if( event.getFromID() == null ) - { - hasEvent = true; - } - else if( from.contentEquals( event.getFromID() ) ) - { - hasEvent = true; - } - } - else - { - hasEvent = true; - } - } - } - - return hasEvent; - } + /** + * Performs a full reset to prepare this object for reuse on a new channel + */ + @Override + public void reset() + { + resetState(); - /** - * Adds the channel and event to the current channel call map. If an entry - * already exists, terminates the event and broadcasts the update. - * - * @param event to place in the map - */ - public void registerCallEvent( P25CallEvent event ) - { - if( mChannelCallMap.containsKey( event.getChannel() ) ) - { - P25CallEvent previousEvent = mChannelCallMap.remove( event.getChannel() ); - - previousEvent.end(); - - broadcast( previousEvent ); - } - - mChannelCallMap.put( event.getChannel(), event ); - } - - private void updateCallEvent( String channel, String from, String to ) - { - P25CallEvent event = mChannelCallMap.get( channel ); - - if( event != null && - to != null && - event.getToID() != null && - event.getToID().contentEquals( to ) ) - { - if( event.getFromID() == null && - from != null && - !from.contentEquals( "0000" ) && - !from.contentEquals( "000000" ) ) - { - event.setFromID( from ); - broadcast( event ); - } - } - } + updateNAC(null); + updateSite(null); + updateSystem(null); + } + + /** + * Resets any temporal state details + */ + private void resetState() + { + mFromTalkgroup = null; + broadcast(ChangedAttribute.FROM_TALKGROUP); + + mFromAlias = null; + broadcast(ChangedAttribute.FROM_TALKGROUP_ALIAS); + + mToTalkgroup = null; + broadcast(ChangedAttribute.TO_TALKGROUP); + + mToAlias = null; + broadcast(ChangedAttribute.TO_TALKGROUP_ALIAS); + + mCallDetectTalkgroups.clear(); + + if(mCurrentCallEvent != null) + { + mCurrentCallEvent.end(); + } + + mCurrentCallEvent = null; + } + + /** + * Performs any initialization operations to prepare for use + */ + @Override + public void init() + { + } + + /** + * Indicates if there a call event exists for the specified channel and + * from and to talkgroup identifiers. + * + * @param channel - channel number + * @param from user id + * @param to talkgroup or user id + * @return true if there is a call event + */ + public boolean hasCallEvent(String channel, String from, String to) + { + boolean hasEvent = false; + + if(mChannelCallMap.containsKey(channel)) + { + P25CallEvent event = mChannelCallMap.get(channel); + + if(to != null && + event.getToID() != null && + to.contentEquals(event.getToID())) + { + if(from != null) + { + if(event.getFromID() == null) + { + hasEvent = true; + } + else if(from.contentEquals(event.getFromID())) + { + hasEvent = true; + } + } + else + { + hasEvent = true; + } + } + } + + return hasEvent; + } + + /** + * Adds the channel and event to the current channel call map. If an entry + * already exists, terminates the event and broadcasts the update. + * + * @param event to place in the map + */ + public void registerCallEvent(P25CallEvent event) + { + if(mChannelCallMap.containsKey(event.getChannel())) + { + P25CallEvent previousEvent = mChannelCallMap.remove(event.getChannel()); + + previousEvent.end(); + + broadcast(previousEvent); + } + + mChannelCallMap.put(event.getChannel(), event); + } + + private void updateCallEvent(String channel, String from, String to) + { + P25CallEvent event = mChannelCallMap.get(channel); + + if(event != null && + to != null && + event.getToID() != null && + event.getToID().contentEquals(to)) + { + if(event.getFromID() == null && + from != null && + !from.contentEquals("0000") && + !from.contentEquals("000000")) + { + event.setFromID(from); + broadcast(event); + } + } + } + + /** + * Primary message processing method. + */ + public void receive(Message message) + { + if(message instanceof P25Message) + { + updateNAC(((P25Message) message).getNAC()); - /** - * Primary message processing method. - */ - public void receive( Message message ) - { - if( message instanceof P25Message ) - { - updateNAC( ( (P25Message)message ).getNAC() ); - /* Voice Vocoder messages */ - if( message instanceof LDUMessage ) - { - processLDU( (LDUMessage)message ); - } + if(message instanceof LDUMessage) + { + processLDU((LDUMessage) message); + } /* Trunking Signalling Messages */ - else if( message instanceof TSBKMessage ) - { - processTSBK( (TSBKMessage)message ); - } + else if(message instanceof TSBKMessage) + { + processTSBK((TSBKMessage) message); + } /* Terminator Data Unit with Link Control Message */ - else if( message instanceof TDULinkControlMessage ) - { - processTDULC( (TDULinkControlMessage)message ); - } + else if(message instanceof TDULinkControlMessage) + { + processTDULC((TDULinkControlMessage) message); + } /* Packet Data Unit Messages */ - else if( message instanceof PDUMessage ) - { - processPDU( (PDUMessage)message ); - } + else if(message instanceof PDUMessage) + { + processPDU((PDUMessage) message); + } /* Header Data Unit Message - preceeds voice LDUs */ - else if( message instanceof HDUMessage ) - { - processHDU( (HDUMessage)message ); - } + else if(message instanceof HDUMessage) + { + processHDU((HDUMessage) message); + } /* Terminator Data Unit, or default message if CRC failed */ - else if( message instanceof TDUMessage ) - { - processTDU( (TDUMessage)message ); - } - } - } + else if(message instanceof TDUMessage) + { + processTDU((TDUMessage) message); + } + } + } - /** - * Terminator Data Unit - */ - private void processTDU( TDUMessage tdu ) - { - } + /** + * Terminator Data Unit + */ + private void processTDU(TDUMessage tdu) + { + } - /** - * Header Data Unit - first message in a call sequence. - */ - private void processHDU( HDUMessage hdu ) - { - if( hdu.isValid() ) - { - broadcast( new DecoderStateEvent( this, Event.START, State.CALL ) ); + /** + * Header Data Unit - first message in a call sequence. + */ + private void processHDU(HDUMessage hdu) + { + if(hdu.isValid()) + { + broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); - String to = hdu.getToID(); - - updateTo( to ); - - if( mCurrentCallEvent == null ) - { - mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( ( hdu.isEncrypted() ? "ENCRYPTED " : "" ) ) - .frequency( mCurrentChannelFrequency ) - .to( to ) - .build(); - - broadcast( mCurrentCallEvent ); - } - - } - } + String to = hdu.getToID(); - /** - * Terminator Data Unit with Link Control - transmitted multiple times at - * beginning or end of call sequence and includes embedded link control messages - */ - private void processTDULC( TDULinkControlMessage tdulc ) - { - if( tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION ) - { - broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) ); - - if( mCurrentCallEvent != null ) - { - mCurrentCallEvent.end(); - mCurrentCallEvent = null; - } - } - else - { - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) ); - } - - switch( tdulc.getOpcode() ) - { - case ADJACENT_SITE_STATUS_BROADCAST: - if( tdulc instanceof AdjacentSiteStatusBroadcast ) - { - IAdjacentSite ias = (IAdjacentSite)tdulc; - - mNeighborMap.put( ias.getUniqueID(), ias ); - - updateSystem( ias.getSystemID() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: - if( tdulc instanceof AdjacentSiteStatusBroadcastExplicit ) - { - IAdjacentSite ias = (IAdjacentSite)tdulc; - - mNeighborMap.put( ias.getUniqueID(), ias ); - - updateSystem( ias.getSystemID() ); - } - break; - case CALL_ALERT: - if( tdulc instanceof module.decode.p25.message.tdu.lc.CallAlert ) - { - module.decode.p25.message.tdu.lc.CallAlert ca = - (module.decode.p25.message.tdu.lc.CallAlert)tdulc; - - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .from( ca.getSourceAddress() ) - .to( ca.getTargetAddress() ) - .details( "CALL ALERT" ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case CALL_TERMINATION_OR_CANCELLATION: + updateTo(to); + + if(mCurrentCallEvent == null) + { + mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details((hdu.isEncrypted() ? "ENCRYPTED " : "")) + .frequency(mCurrentChannelFrequency) + .to(to) + .build(); + + broadcast(mCurrentCallEvent); + } + + } + } + + /** + * Terminator Data Unit with Link Control - transmitted multiple times at + * beginning or end of call sequence and includes embedded link control messages + */ + private void processTDULC(TDULinkControlMessage tdulc) + { + if(tdulc.getOpcode() == LinkControlOpcode.CALL_TERMINATION_OR_CANCELLATION) + { + broadcast(new DecoderStateEvent(this, Event.END, State.FADE)); + + if(mCurrentCallEvent != null) + { + mCurrentCallEvent.end(); + mCurrentCallEvent = null; + } + } + else + { + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); + } + + switch(tdulc.getOpcode()) + { + case ADJACENT_SITE_STATUS_BROADCAST: + if(tdulc instanceof AdjacentSiteStatusBroadcast) + { + IAdjacentSite ias = (IAdjacentSite) tdulc; + + mNeighborMap.put(ias.getUniqueID(), ias); + + updateSystem(ias.getSystemID()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: + if(tdulc instanceof AdjacentSiteStatusBroadcastExplicit) + { + IAdjacentSite ias = (IAdjacentSite) tdulc; + + mNeighborMap.put(ias.getUniqueID(), ias); + + updateSystem(ias.getSystemID()); + } + break; + case CALL_ALERT: + if(tdulc instanceof module.decode.p25.message.tdu.lc.CallAlert) + { + module.decode.p25.message.tdu.lc.CallAlert ca = + (module.decode.p25.message.tdu.lc.CallAlert) tdulc; + + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .from(ca.getSourceAddress()) + .to(ca.getTargetAddress()) + .details("CALL ALERT") + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case CALL_TERMINATION_OR_CANCELLATION: /* This opcode as handled at the beginning of the method */ - break; - case CHANNEL_IDENTIFIER_UPDATE: - //TODO: does the activity summary need this message? + break; + case CHANNEL_IDENTIFIER_UPDATE: + //TODO: does the activity summary need this message? - /* This message is handled by the P25MessageProcessor and + /* This message is handled by the P25MessageProcessor and * inserted into any channels needing frequency band info */ - break; - case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: - //TODO: does the activity summary need this message? + break; + case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: + //TODO: does the activity summary need this message? - /* This message is handled by the P25MessageProcessor and + /* This message is handled by the P25MessageProcessor and * inserted into any channels needing frequency band info */ - break; - case EXTENDED_FUNCTION_COMMAND: - if( tdulc instanceof module.decode.p25.message.tdu.lc.ExtendedFunctionCommand ) - { - module.decode.p25.message.tdu.lc.ExtendedFunctionCommand efc = - (module.decode.p25.message.tdu.lc.ExtendedFunctionCommand)tdulc; - - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .to( efc.getTargetAddress() ) - .details( "FUNCTION:" + efc.getExtendedFunction().getLabel() + - " ARG:" + efc.getArgument() ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case GROUP_AFFILIATION_QUERY: - if( tdulc instanceof module.decode.p25.message.tdu.lc.GroupAffiliationQuery ) - { - module.decode.p25.message.tdu.lc.GroupAffiliationQuery gaq = - (module.decode.p25.message.tdu.lc.GroupAffiliationQuery)tdulc; + break; + case EXTENDED_FUNCTION_COMMAND: + if(tdulc instanceof module.decode.p25.message.tdu.lc.ExtendedFunctionCommand) + { + module.decode.p25.message.tdu.lc.ExtendedFunctionCommand efc = + (module.decode.p25.message.tdu.lc.ExtendedFunctionCommand) tdulc; - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .details( "GROUP AFFILIATION QUERY" ) - .from( gaq.getSourceAddress() ) - .to( gaq.getTargetAddress() ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case GROUP_VOICE_CHANNEL_UPDATE: - /* Used only on trunked systems on the outbound channel, to - * reflect user activity on other channels. We process this + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .to(efc.getTargetAddress()) + .details("FUNCTION:" + efc.getExtendedFunction().getLabel() + + " ARG:" + efc.getArgument()) + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case GROUP_AFFILIATION_QUERY: + if(tdulc instanceof module.decode.p25.message.tdu.lc.GroupAffiliationQuery) + { + module.decode.p25.message.tdu.lc.GroupAffiliationQuery gaq = + (module.decode.p25.message.tdu.lc.GroupAffiliationQuery) tdulc; + + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .details("GROUP AFFILIATION QUERY") + .from(gaq.getSourceAddress()) + .to(gaq.getTargetAddress()) + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case GROUP_VOICE_CHANNEL_UPDATE: + /* Used only on trunked systems on the outbound channel, to + * reflect user activity on other channels. We process this * as a call detect */ - if( tdulc instanceof GroupVoiceChannelUpdate ) - { - GroupVoiceChannelUpdate gvcu = (GroupVoiceChannelUpdate)tdulc; + if(tdulc instanceof GroupVoiceChannelUpdate) + { + GroupVoiceChannelUpdate gvcu = (GroupVoiceChannelUpdate) tdulc; - String groupA = gvcu.getGroupAddressA(); + String groupA = gvcu.getGroupAddressA(); - if( !mCallDetectTalkgroups.contains( groupA ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) - .aliasList( getAliasList() ) - .channel( gvcu.getChannelA() ) - .details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) ) - .frequency( gvcu.getDownlinkFrequencyA() ) - .to( groupA ) - .build() ); - - mCallDetectTalkgroups.add( groupA ); - } - - String groupB = gvcu.getGroupAddressB(); + if(!mCallDetectTalkgroups.contains(groupA)) + { + broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) + .aliasList(getAliasList()) + .channel(gvcu.getChannelA()) + .details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) + .frequency(gvcu.getDownlinkFrequencyA()) + .to(groupA) + .build()); - if( !mCallDetectTalkgroups.contains( groupB ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) - .aliasList( getAliasList() ) - .channel( gvcu.getChannelB() ) - .details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) ) - .frequency( gvcu.getDownlinkFrequencyB() ) - .to( groupB ) - .build() ); - - mCallDetectTalkgroups.add( groupB ); - } - } - break; - case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: + mCallDetectTalkgroups.add(groupA); + } + + String groupB = gvcu.getGroupAddressB(); + + if(!mCallDetectTalkgroups.contains(groupB)) + { + broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) + .aliasList(getAliasList()) + .channel(gvcu.getChannelB()) + .details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) + .frequency(gvcu.getDownlinkFrequencyB()) + .to(groupB) + .build()); + + mCallDetectTalkgroups.add(groupB); + } + } + break; + case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: /* Reflects other call activity on the system: CALL DETECT */ - if( mChannelType == ChannelType.STANDARD && - tdulc instanceof GroupVoiceChannelUpdateExplicit ) - { - GroupVoiceChannelUpdateExplicit gvcue = - (GroupVoiceChannelUpdateExplicit)tdulc; - - String group = gvcue.getGroupAddress(); - - if( !mCallDetectTalkgroups.contains( group ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) - .aliasList( getAliasList() ) - .channel( gvcue.getTransmitChannel() ) - .details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) ) - .frequency( gvcue.getDownlinkFrequency() ) - .to( group ) - .build() ); - - mCallDetectTalkgroups.add( group ); - } - } - break; - case GROUP_VOICE_CHANNEL_USER: + if(mChannelType == ChannelType.STANDARD && + tdulc instanceof GroupVoiceChannelUpdateExplicit) + { + GroupVoiceChannelUpdateExplicit gvcue = + (GroupVoiceChannelUpdateExplicit) tdulc; + + String group = gvcue.getGroupAddress(); + + if(!mCallDetectTalkgroups.contains(group)) + { + broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) + .aliasList(getAliasList()) + .channel(gvcue.getTransmitChannel()) + .details((gvcue.isEncrypted() ? "ENCRYPTED" : "")) + .frequency(gvcue.getDownlinkFrequency()) + .to(group) + .build()); + + mCallDetectTalkgroups.add(group); + } + } + break; + case GROUP_VOICE_CHANNEL_USER: /* Used on a traffic channel to reflect current call entities */ - if( tdulc instanceof module.decode.p25.message.tdu.lc.GroupVoiceChannelUser ) - { - module.decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser = - (module.decode.p25.message.tdu.lc.GroupVoiceChannelUser)tdulc; + if(tdulc instanceof module.decode.p25.message.tdu.lc.GroupVoiceChannelUser) + { + module.decode.p25.message.tdu.lc.GroupVoiceChannelUser gvcuser = + (module.decode.p25.message.tdu.lc.GroupVoiceChannelUser) tdulc; - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) ); - - String from = gvcuser.getSourceAddress(); - String to = gvcuser.getGroupAddress(); - - updateFrom( from ); - updateTo( to ); + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); - if( mCurrentCallEvent == null ) - { - mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( "TDULC GROUP VOICE CHANNEL USER" + - ( gvcuser.isEncrypted() ? "ENCRYPTED " : "" ) + - ( gvcuser.isEmergency() ? "EMERGENCY " : "") ) - .frequency( mCurrentChannelFrequency ) - .from( from ) - .to( to ) - .build(); - - broadcast( mCurrentCallEvent ); - } - } - break; - case MESSAGE_UPDATE: - if( tdulc instanceof module.decode.p25.message.tdu.lc.MessageUpdate ) - { - module.decode.p25.message.tdu.lc.MessageUpdate mu = - (module.decode.p25.message.tdu.lc.MessageUpdate)tdulc; - - broadcast( new P25CallEvent.Builder( CallEventType.SDM ) - .aliasList( getAliasList() ) - .from( mu.getSourceAddress() ) - .to( mu.getTargetAddress() ) - .details( "MSG: " + mu.getShortDataMessage() ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case NETWORK_STATUS_BROADCAST: - if( tdulc instanceof NetworkStatusBroadcast ) - { - updateSystem( ((NetworkStatusBroadcast)tdulc).getSystem() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case NETWORK_STATUS_BROADCAST_EXPLICIT: - if( tdulc instanceof NetworkStatusBroadcastExplicit ) - { - updateSystem( ((NetworkStatusBroadcastExplicit)tdulc).getSystem() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case PROTECTION_PARAMETER_BROADCAST: - if( tdulc instanceof module.decode.p25.message.tdu.lc.ProtectionParameterBroadcast ) - { - module.decode.p25.message.tdu.lc.ProtectionParameterBroadcast ppb = - (module.decode.p25.message.tdu.lc.ProtectionParameterBroadcast)tdulc; + String from = gvcuser.getSourceAddress(); + String to = gvcuser.getGroupAddress(); - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .to( ppb.getTargetAddress() ) - .details( "ENCRYPTION: " + - ppb.getEncryption().name() + " KEY:" + - ppb.getEncryptionKey() ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case RFSS_STATUS_BROADCAST: - if( tdulc instanceof module.decode.p25.message.tdu.lc.RFSSStatusBroadcast ) - { - module.decode.p25.message.tdu.lc.RFSSStatusBroadcast rfsssb = - (module.decode.p25.message.tdu.lc.RFSSStatusBroadcast)tdulc; - - updateSystem( rfsssb.getSystem() ); + updateFrom(from); + updateTo(to); - String site = rfsssb.getRFSubsystemID() + "-" + - rfsssb.getSiteID(); + if(mCurrentCallEvent == null) + { + mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.GROUP_CALL) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details("TDULC GROUP VOICE CHANNEL USER" + + (gvcuser.isEncrypted() ? "ENCRYPTED " : "") + + (gvcuser.isEmergency() ? "EMERGENCY " : "")) + .frequency(mCurrentChannelFrequency) + .from(from) + .to(to) + .build(); - updateSite( site ); - - if( mCurrentChannel == null || - !mCurrentChannel.contentEquals( rfsssb.getChannel() ) ) - { - mCurrentChannel = rfsssb.getChannel(); - mCurrentChannelFrequency = rfsssb.getDownlinkFrequency(); - } - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case RFSS_STATUS_BROADCAST_EXPLICIT: - if( tdulc instanceof module.decode.p25.message.tdu.lc.RFSSStatusBroadcastExplicit ) - { - module.decode.p25.message.tdu.lc.RFSSStatusBroadcastExplicit rfsssbe = - (module.decode.p25.message.tdu.lc.RFSSStatusBroadcastExplicit)tdulc; - - String site = rfsssbe.getRFSubsystemID() + "-" + - rfsssbe.getSiteID(); + broadcast(mCurrentCallEvent); + } + } + break; + case MESSAGE_UPDATE: + if(tdulc instanceof module.decode.p25.message.tdu.lc.MessageUpdate) + { + module.decode.p25.message.tdu.lc.MessageUpdate mu = + (module.decode.p25.message.tdu.lc.MessageUpdate) tdulc; - updateSite( site ); - - if( mCurrentChannel == null || - !mCurrentChannel.contentEquals( rfsssbe.getTransmitChannel() ) ) - { - mCurrentChannel = rfsssbe.getTransmitChannel(); - mCurrentChannelFrequency = rfsssbe.getDownlinkFrequency(); - } - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case SECONDARY_CONTROL_CHANNEL_BROADCAST: - if( tdulc instanceof SecondaryControlChannelBroadcast ) - { - SecondaryControlChannelBroadcast sccb = - (SecondaryControlChannelBroadcast)tdulc; - - String site = sccb.getRFSubsystemID() + "-" + - sccb.getSiteID(); + broadcast(new P25CallEvent.Builder(CallEventType.SDM) + .aliasList(getAliasList()) + .from(mu.getSourceAddress()) + .to(mu.getTargetAddress()) + .details("MSG: " + mu.getShortDataMessage()) + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case NETWORK_STATUS_BROADCAST: + if(tdulc instanceof NetworkStatusBroadcast) + { + updateSystem(((NetworkStatusBroadcast) tdulc).getSystem()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case NETWORK_STATUS_BROADCAST_EXPLICIT: + if(tdulc instanceof NetworkStatusBroadcastExplicit) + { + updateSystem(((NetworkStatusBroadcastExplicit) tdulc).getSystem()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case PROTECTION_PARAMETER_BROADCAST: + if(tdulc instanceof module.decode.p25.message.tdu.lc.ProtectionParameterBroadcast) + { + module.decode.p25.message.tdu.lc.ProtectionParameterBroadcast ppb = + (module.decode.p25.message.tdu.lc.ProtectionParameterBroadcast) tdulc; - updateSite( site ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - if( tdulc instanceof SecondaryControlChannelBroadcastExplicit ) - { - SecondaryControlChannelBroadcastExplicit sccb = - (SecondaryControlChannelBroadcastExplicit)tdulc; - - String site = sccb.getRFSubsystemID() + "-" + - sccb.getSiteID(); + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .to(ppb.getTargetAddress()) + .details("ENCRYPTION: " + + ppb.getEncryption().name() + " KEY:" + + ppb.getEncryptionKey()) + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case RFSS_STATUS_BROADCAST: + if(tdulc instanceof module.decode.p25.message.tdu.lc.RFSSStatusBroadcast) + { + module.decode.p25.message.tdu.lc.RFSSStatusBroadcast rfsssb = + (module.decode.p25.message.tdu.lc.RFSSStatusBroadcast) tdulc; - updateSite( site ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case STATUS_QUERY: - if( tdulc instanceof module.decode.p25.message.tdu.lc.StatusQuery ) - { - module.decode.p25.message.tdu.lc.StatusQuery sq = - (module.decode.p25.message.tdu.lc.StatusQuery)tdulc; + updateSystem(rfsssb.getSystem()); - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .details( "STATUS QUERY" ) - .from( sq.getSourceAddress() ) - .to( sq.getTargetAddress() ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case STATUS_UPDATE: - if( tdulc instanceof module.decode.p25.message.tdu.lc.StatusUpdate ) - { - module.decode.p25.message.tdu.lc.StatusUpdate su = - (module.decode.p25.message.tdu.lc.StatusUpdate)tdulc; + String site = rfsssb.getRFSubsystemID() + "-" + + rfsssb.getSiteID(); - broadcast( new P25CallEvent.Builder( CallEventType.STATUS ) - .aliasList( getAliasList() ) - .details( "STATUS UNIT:" + su.getUnitStatus() + - " USER:" + su.getUserStatus() ) - .from( su.getSourceAddress() ) - .to( su.getTargetAddress() ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case SYSTEM_SERVICE_BROADCAST: + updateSite(site); + + if(mCurrentChannel == null || + !mCurrentChannel.contentEquals(rfsssb.getChannel())) + { + mCurrentChannel = rfsssb.getChannel(); + mCurrentChannelFrequency = rfsssb.getDownlinkFrequency(); + } + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case RFSS_STATUS_BROADCAST_EXPLICIT: + if(tdulc instanceof module.decode.p25.message.tdu.lc.RFSSStatusBroadcastExplicit) + { + module.decode.p25.message.tdu.lc.RFSSStatusBroadcastExplicit rfsssbe = + (module.decode.p25.message.tdu.lc.RFSSStatusBroadcastExplicit) tdulc; + + String site = rfsssbe.getRFSubsystemID() + "-" + + rfsssbe.getSiteID(); + + updateSite(site); + + if(mCurrentChannel == null || + !mCurrentChannel.contentEquals(rfsssbe.getTransmitChannel())) + { + mCurrentChannel = rfsssbe.getTransmitChannel(); + mCurrentChannelFrequency = rfsssbe.getDownlinkFrequency(); + } + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case SECONDARY_CONTROL_CHANNEL_BROADCAST: + if(tdulc instanceof SecondaryControlChannelBroadcast) + { + SecondaryControlChannelBroadcast sccb = + (SecondaryControlChannelBroadcast) tdulc; + + String site = sccb.getRFSubsystemID() + "-" + + sccb.getSiteID(); + + updateSite(site); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + if(tdulc instanceof SecondaryControlChannelBroadcastExplicit) + { + SecondaryControlChannelBroadcastExplicit sccb = + (SecondaryControlChannelBroadcastExplicit) tdulc; + + String site = sccb.getRFSubsystemID() + "-" + + sccb.getSiteID(); + + updateSite(site); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case STATUS_QUERY: + if(tdulc instanceof module.decode.p25.message.tdu.lc.StatusQuery) + { + module.decode.p25.message.tdu.lc.StatusQuery sq = + (module.decode.p25.message.tdu.lc.StatusQuery) tdulc; + + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .details("STATUS QUERY") + .from(sq.getSourceAddress()) + .to(sq.getTargetAddress()) + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case STATUS_UPDATE: + if(tdulc instanceof module.decode.p25.message.tdu.lc.StatusUpdate) + { + module.decode.p25.message.tdu.lc.StatusUpdate su = + (module.decode.p25.message.tdu.lc.StatusUpdate) tdulc; + + broadcast(new P25CallEvent.Builder(CallEventType.STATUS) + .aliasList(getAliasList()) + .details("STATUS UNIT:" + su.getUnitStatus() + + " USER:" + su.getUserStatus()) + .from(su.getSourceAddress()) + .to(su.getTargetAddress()) + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case SYSTEM_SERVICE_BROADCAST: /* This message doesn't provide anything we need for channel state */ - break; - case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - if( tdulc instanceof module.decode.p25.message.tdu.lc.TelephoneInterconnectAnswerRequest ) - { - module.decode.p25.message.tdu.lc.TelephoneInterconnectAnswerRequest tiar = - (module.decode.p25.message.tdu.lc.TelephoneInterconnectAnswerRequest)tdulc; + break; + case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: + if(tdulc instanceof module.decode.p25.message.tdu.lc.TelephoneInterconnectAnswerRequest) + { + module.decode.p25.message.tdu.lc.TelephoneInterconnectAnswerRequest tiar = + (module.decode.p25.message.tdu.lc.TelephoneInterconnectAnswerRequest) tdulc; - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .from( tiar.getTelephoneNumber() ) - .to( tiar.getTargetAddress() ) - .details( "TELEPHONE CALL ALERT" ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: - if( mChannelType == ChannelType.STANDARD && - tdulc instanceof module.decode.p25.message.tdu.lc.TelephoneInterconnectVoiceChannelUser ) - { - module.decode.p25.message.tdu.lc.TelephoneInterconnectVoiceChannelUser tivcu = - (module.decode.p25.message.tdu.lc.TelephoneInterconnectVoiceChannelUser)tdulc; - - String to = tivcu.getAddress(); - - updateTo( to ); - - if( mCurrentCallEvent == null ) - { - mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( ( tivcu.isEncrypted() ? "ENCRYPTED" : "" ) + - ( tivcu.isEmergency() ? " EMERGENCY" : "") ) - .frequency( mCurrentChannelFrequency ) - .to( to ) - .build(); - - broadcast( mCurrentCallEvent ); - } - } - break; - case UNIT_AUTHENTICATION_COMMAND: - if( tdulc instanceof module.decode.p25.message.tdu.lc.UnitAuthenticationCommand ) - { - module.decode.p25.message.tdu.lc.UnitAuthenticationCommand uac = - (module.decode.p25.message.tdu.lc.UnitAuthenticationCommand)tdulc; - - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .to( uac.getCompleteTargetAddress() ) - .details( "AUTHENTICATE" ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case UNIT_REGISTRATION_COMMAND: - if( tdulc instanceof module.decode.p25.message.tdu.lc.UnitRegistrationCommand ) - { - module.decode.p25.message.tdu.lc.UnitRegistrationCommand urc = - (module.decode.p25.message.tdu.lc.UnitRegistrationCommand)tdulc; + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .from(tiar.getTelephoneNumber()) + .to(tiar.getTargetAddress()) + .details("TELEPHONE CALL ALERT") + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: + if(mChannelType == ChannelType.STANDARD && + tdulc instanceof module.decode.p25.message.tdu.lc.TelephoneInterconnectVoiceChannelUser) + { + module.decode.p25.message.tdu.lc.TelephoneInterconnectVoiceChannelUser tivcu = + (module.decode.p25.message.tdu.lc.TelephoneInterconnectVoiceChannelUser) tdulc; - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .to( urc.getCompleteTargetAddress() ) - .details( "REGISTER" ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case UNIT_TO_UNIT_ANSWER_REQUEST: - if( tdulc instanceof module.decode.p25.message.tdu.lc.UnitToUnitAnswerRequest ) - { - module.decode.p25.message.tdu.lc.UnitToUnitAnswerRequest uuar = - (module.decode.p25.message.tdu.lc.UnitToUnitAnswerRequest)tdulc; + String to = tivcu.getAddress(); - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .from( uuar.getSourceAddress() ) - .to( uuar.getTargetAddress() ) - .details( "UNIT TO UNIT CALL ALERT" ) - .build() ); - } - else - { - logAlternateVendorMessage( tdulc ); - } - break; - case UNIT_TO_UNIT_VOICE_CHANNEL_USER: + updateTo(to); + + if(mCurrentCallEvent == null) + { + mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.TELEPHONE_INTERCONNECT) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details((tivcu.isEncrypted() ? "ENCRYPTED" : "") + + (tivcu.isEmergency() ? " EMERGENCY" : "")) + .frequency(mCurrentChannelFrequency) + .to(to) + .build(); + + broadcast(mCurrentCallEvent); + } + } + break; + case UNIT_AUTHENTICATION_COMMAND: + if(tdulc instanceof module.decode.p25.message.tdu.lc.UnitAuthenticationCommand) + { + module.decode.p25.message.tdu.lc.UnitAuthenticationCommand uac = + (module.decode.p25.message.tdu.lc.UnitAuthenticationCommand) tdulc; + + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .to(uac.getCompleteTargetAddress()) + .details("AUTHENTICATE") + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case UNIT_REGISTRATION_COMMAND: + if(tdulc instanceof module.decode.p25.message.tdu.lc.UnitRegistrationCommand) + { + module.decode.p25.message.tdu.lc.UnitRegistrationCommand urc = + (module.decode.p25.message.tdu.lc.UnitRegistrationCommand) tdulc; + + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .to(urc.getCompleteTargetAddress()) + .details("REGISTER") + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case UNIT_TO_UNIT_ANSWER_REQUEST: + if(tdulc instanceof module.decode.p25.message.tdu.lc.UnitToUnitAnswerRequest) + { + module.decode.p25.message.tdu.lc.UnitToUnitAnswerRequest uuar = + (module.decode.p25.message.tdu.lc.UnitToUnitAnswerRequest) tdulc; + + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .from(uuar.getSourceAddress()) + .to(uuar.getTargetAddress()) + .details("UNIT TO UNIT CALL ALERT") + .build()); + } + else + { + logAlternateVendorMessage(tdulc); + } + break; + case UNIT_TO_UNIT_VOICE_CHANNEL_USER: /* Used on traffic channels to indicate the current call entities */ - if( tdulc instanceof module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser ) - { - module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser uuvcu = - (module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser)tdulc; + if(tdulc instanceof module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser) + { + module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser uuvcu = + (module.decode.p25.message.tdu.lc.UnitToUnitVoiceChannelUser) tdulc; - String from = uuvcu.getSourceAddress(); - updateFrom( from ); + String from = uuvcu.getSourceAddress(); + updateFrom(from); - String to = uuvcu.getTargetAddress(); - updateTo( to ); - - if( mCurrentCallEvent != null ) - { - mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.UNIT_TO_UNIT_CALL ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( ( uuvcu.isEncrypted() ? "ENCRYPTED " : "" ) + - ( uuvcu.isEmergency() ? "EMERGENCY " : "" ) ) - .frequency( mCurrentChannelFrequency ) - .from( from ) - .to( to ) - .build(); - } - } - break; - default: - break; - } - } - - /** - * Log optional vendor format messages that we don't yet support so that we - * can understand those messages and eventually add support. - */ - private void logAlternateVendorMessage( P25Message message ) - { + String to = uuvcu.getTargetAddress(); + updateTo(to); + + if(mCurrentCallEvent != null) + { + mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.UNIT_TO_UNIT_CALL) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details((uuvcu.isEncrypted() ? "ENCRYPTED " : "") + + (uuvcu.isEmergency() ? "EMERGENCY " : "")) + .frequency(mCurrentChannelFrequency) + .from(from) + .to(to) + .build(); + } + } + break; + default: + break; + } + } + + /** + * Log optional vendor format messages that we don't yet support so that we + * can understand those messages and eventually add support. + */ + private void logAlternateVendorMessage(P25Message message) + { // if( message.isValid() ) // { // mLog.info( "PLEASE NOTIFY DEVELOPER - UNRECOGNIZED P25 VENDOR FORMAT -" @@ -956,2734 +959,2875 @@ public class P25DecoderState extends DecoderState // + " MSG:" + message.getBinaryMessage() // + " CLASS:" + message.getClass() ); // } - } - - /** - * Processes LDU voice frame messages. Sends continuation events to keep - * the channel state synchronized and processes the embedded link control - * messages to capture/broadcast peripheral events like paging. - */ - private void processLDU( LDUMessage ldu ) - { - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CALL ) ); - - if( mCurrentCallEvent == null ) - { - mCurrentCallEvent = new P25CallEvent.Builder( CallEventType.CALL ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .frequency( mCurrentChannelFrequency ) - .build(); - - broadcast( mCurrentCallEvent ); - } - - if( ldu instanceof LDU1Message ) - { - switch( ((LDU1Message)ldu).getOpcode() ) - { - case ADJACENT_SITE_STATUS_BROADCAST: - if( ldu instanceof module.decode.p25.message.ldu.lc.AdjacentSiteStatusBroadcast ) - { - IAdjacentSite ias = (IAdjacentSite)ldu; - - mNeighborMap.put( ias.getUniqueID(), ias ); - - updateSystem( ias.getSystemID() ); - } - else - { - logAlternateVendorMessage( ldu ); - } + } - break; - case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: - if( ldu instanceof module.decode.p25.message.ldu.lc.AdjacentSiteStatusBroadcastExplicit ) - { - IAdjacentSite ias = (IAdjacentSite)ldu; - - mNeighborMap.put( ias.getUniqueID(), ias ); - - updateSystem( ias.getSystemID() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case CALL_ALERT: - if( ldu instanceof module.decode.p25.message.ldu.lc.CallAlert ) - { - module.decode.p25.message.ldu.lc.CallAlert ca = - (module.decode.p25.message.ldu.lc.CallAlert)ldu; - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .from( ca.getSourceAddress() ) - .to( ca.getTargetAddress() ) - .details( "CALL ALERT" ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case CALL_TERMINATION_OR_CANCELLATION: - broadcast( new DecoderStateEvent( this, Event.END, State.FADE ) ); - mCurrentCallEvent = null; + /** + * Processes LDU voice frame messages. Sends continuation events to keep + * the channel state synchronized and processes the embedded link control + * messages to capture/broadcast peripheral events like paging. + */ + private void processLDU(LDUMessage ldu) + { + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL)); - if( !( ldu instanceof CallTermination ) ) - { - logAlternateVendorMessage( ldu ); - } - break; - case CHANNEL_IDENTIFIER_UPDATE: - //TODO: do we need this for the activity summary? + if(mCurrentCallEvent == null) + { + mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .frequency(mCurrentChannelFrequency) + .build(); - /* This message is handled by the P25MessageProcessor and + broadcast(mCurrentCallEvent); + } + + if(ldu instanceof LDU1Message) + { + switch(((LDU1Message) ldu).getOpcode()) + { + case ADJACENT_SITE_STATUS_BROADCAST: + if(ldu instanceof module.decode.p25.message.ldu.lc.AdjacentSiteStatusBroadcast) + { + IAdjacentSite ias = (IAdjacentSite) ldu; + + mNeighborMap.put(ias.getUniqueID(), ias); + + updateSystem(ias.getSystemID()); + } + else + { + logAlternateVendorMessage(ldu); + } + + break; + case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT: + if(ldu instanceof module.decode.p25.message.ldu.lc.AdjacentSiteStatusBroadcastExplicit) + { + IAdjacentSite ias = (IAdjacentSite) ldu; + + mNeighborMap.put(ias.getUniqueID(), ias); + + updateSystem(ias.getSystemID()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case CALL_ALERT: + if(ldu instanceof module.decode.p25.message.ldu.lc.CallAlert) + { + module.decode.p25.message.ldu.lc.CallAlert ca = + (module.decode.p25.message.ldu.lc.CallAlert) ldu; + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .from(ca.getSourceAddress()) + .to(ca.getTargetAddress()) + .details("CALL ALERT") + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case CALL_TERMINATION_OR_CANCELLATION: + broadcast(new DecoderStateEvent(this, Event.END, State.FADE)); + mCurrentCallEvent = null; + + if(!(ldu instanceof CallTermination)) + { + logAlternateVendorMessage(ldu); + } + break; + case CHANNEL_IDENTIFIER_UPDATE: + //TODO: do we need this for the activity summary? + + /* This message is handled by the P25MessageProcessor and * inserted into any channels needing frequency band info */ - break; - case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: - //TODO: do we need this for the activity summary? + break; + case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT: + //TODO: do we need this for the activity summary? - /* This message is handled by the P25MessageProcessor and + /* This message is handled by the P25MessageProcessor and * inserted into any channels needing frequency band info */ - break; - case EXTENDED_FUNCTION_COMMAND: - if( ldu instanceof module.decode.p25.message.ldu.lc.ExtendedFunctionCommand ) - { - module.decode.p25.message.ldu.lc.ExtendedFunctionCommand efc = - (module.decode.p25.message.ldu.lc.ExtendedFunctionCommand)ldu; + break; + case EXTENDED_FUNCTION_COMMAND: + if(ldu instanceof module.decode.p25.message.ldu.lc.ExtendedFunctionCommand) + { + module.decode.p25.message.ldu.lc.ExtendedFunctionCommand efc = + (module.decode.p25.message.ldu.lc.ExtendedFunctionCommand) ldu; - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .to( efc.getTargetAddress() ) - .details( "FUNCTION:" + efc.getExtendedFunction().getLabel() + - " ARG:" + efc.getArgument() ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case GROUP_AFFILIATION_QUERY: - if( mChannelType == ChannelType.STANDARD && - ldu instanceof module.decode.p25.message.ldu.lc.GroupAffiliationQuery ) - { - module.decode.p25.message.ldu.lc.GroupAffiliationQuery gaq = - (module.decode.p25.message.ldu.lc.GroupAffiliationQuery)ldu; - - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .details( "GROUP AFFILIATION QUERY" ) - .from( gaq.getSourceAddress() ) - .to( gaq.getTargetAddress() ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case GROUP_VOICE_CHANNEL_UPDATE: - if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate ) - { - module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate gvcu = - (module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate)ldu; + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .to(efc.getTargetAddress()) + .details("FUNCTION:" + efc.getExtendedFunction().getLabel() + + " ARG:" + efc.getArgument()) + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case GROUP_AFFILIATION_QUERY: + if(mChannelType == ChannelType.STANDARD && + ldu instanceof module.decode.p25.message.ldu.lc.GroupAffiliationQuery) + { + module.decode.p25.message.ldu.lc.GroupAffiliationQuery gaq = + (module.decode.p25.message.ldu.lc.GroupAffiliationQuery) ldu; - String userA = gvcu.getGroupAddressA(); - String userB = gvcu.getGroupAddressB(); - - if( mChannelType == ChannelType.STANDARD ) - { - if( !mCallDetectTalkgroups.contains( userA ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) - .aliasList( getAliasList() ) - .channel( gvcu.getChannelA() ) - .details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) ) - .frequency( gvcu.getDownlinkFrequencyA() ) - .to( userA ) - .build() ); - - mCallDetectTalkgroups.add( userA ); - } - - if( userB != null && - !userB.contentEquals( "0000" ) && - !mCallDetectTalkgroups.contains( userB ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) - .aliasList( getAliasList() ) - .channel( gvcu.getChannelB() ) - .details( ( gvcu.isEncrypted() ? "ENCRYPTED" : "" ) ) - .frequency( gvcu.getDownlinkFrequencyB() ) - .to( userB ) - .build() ); - - mCallDetectTalkgroups.add( userB ); - } - } - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: - if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit ) - { - module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit gvcue = - (module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit)ldu; - - String group = gvcue.getGroupAddress(); + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .details("GROUP AFFILIATION QUERY") + .from(gaq.getSourceAddress()) + .to(gaq.getTargetAddress()) + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case GROUP_VOICE_CHANNEL_UPDATE: + if(ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate) + { + module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate gvcu = + (module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdate) ldu; - if( mChannelType == ChannelType.STANDARD ) - { - if( !mCallDetectTalkgroups.contains( group ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.CALL_DETECT ) - .aliasList( getAliasList() ) - .channel( gvcue.getTransmitChannel() ) - .details( ( gvcue.isEncrypted() ? "ENCRYPTED" : "" ) ) - .frequency( gvcue.getDownlinkFrequency() ) - .to( group ) - .build() ); - - mCallDetectTalkgroups.add( group ); - } - } - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case GROUP_VOICE_CHANNEL_USER: + String userA = gvcu.getGroupAddressA(); + String userB = gvcu.getGroupAddressB(); + + if(mChannelType == ChannelType.STANDARD) + { + if(!mCallDetectTalkgroups.contains(userA)) + { + broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) + .aliasList(getAliasList()) + .channel(gvcu.getChannelA()) + .details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) + .frequency(gvcu.getDownlinkFrequencyA()) + .to(userA) + .build()); + + mCallDetectTalkgroups.add(userA); + } + + if(userB != null && + !userB.contentEquals("0000") && + !mCallDetectTalkgroups.contains(userB)) + { + broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) + .aliasList(getAliasList()) + .channel(gvcu.getChannelB()) + .details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) + .frequency(gvcu.getDownlinkFrequencyB()) + .to(userB) + .build()); + + mCallDetectTalkgroups.add(userB); + } + } + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT: + if(ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit) + { + module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit gvcue = + (module.decode.p25.message.ldu.lc.GroupVoiceChannelUpdateExplicit) ldu; + + String group = gvcue.getGroupAddress(); + + if(mChannelType == ChannelType.STANDARD) + { + if(!mCallDetectTalkgroups.contains(group)) + { + broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) + .aliasList(getAliasList()) + .channel(gvcue.getTransmitChannel()) + .details((gvcue.isEncrypted() ? "ENCRYPTED" : "")) + .frequency(gvcue.getDownlinkFrequency()) + .to(group) + .build()); + + mCallDetectTalkgroups.add(group); + } + } + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case GROUP_VOICE_CHANNEL_USER: /* Indicates the current user of the current channel */ - if( ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser ) - { - module.decode.p25.message.ldu.lc.GroupVoiceChannelUser gvcuser = - (module.decode.p25.message.ldu.lc.GroupVoiceChannelUser)ldu; + if(ldu instanceof module.decode.p25.message.ldu.lc.GroupVoiceChannelUser) + { + module.decode.p25.message.ldu.lc.GroupVoiceChannelUser gvcuser = + (module.decode.p25.message.ldu.lc.GroupVoiceChannelUser) ldu; - updateFrom( gvcuser.getSourceAddress() ); - - updateTo( gvcuser.getGroupAddress() ); - - if( mChannelType == ChannelType.STANDARD ) - { - if( mCurrentCallEvent.getCallEventType() != CallEventType.GROUP_CALL ) - { - mCurrentCallEvent.setCallEventType( CallEventType.GROUP_CALL ); - broadcast( mCurrentCallEvent ); - } + updateFrom(gvcuser.getSourceAddress()); - if( mCurrentCallEvent.getDetails() == null ) - { - mCurrentCallEvent.setDetails( - ( gvcuser.isEncrypted() ? "ENCRYPTED " : "" ) + - ( gvcuser.isEmergency() ? "EMERGENCY " : "" ) ); + updateTo(gvcuser.getGroupAddress()); - broadcast( mCurrentCallEvent ); - } - } - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case MESSAGE_UPDATE: - if( ldu instanceof module.decode.p25.message.ldu.lc.MessageUpdate ) - { - module.decode.p25.message.ldu.lc.MessageUpdate mu = - (module.decode.p25.message.ldu.lc.MessageUpdate)ldu; + if(mChannelType == ChannelType.STANDARD) + { + if(mCurrentCallEvent.getCallEventType() != CallEventType.GROUP_CALL) + { + mCurrentCallEvent.setCallEventType(CallEventType.GROUP_CALL); + broadcast(mCurrentCallEvent); + } - broadcast( new P25CallEvent.Builder( CallEventType.SDM ) - .aliasList( getAliasList() ) - .from( mu.getSourceAddress() ) - .to( mu.getTargetAddress() ) - .details( "MSG: " + mu.getShortDataMessage() ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case NETWORK_STATUS_BROADCAST: - if( ldu instanceof module.decode.p25.message.ldu.lc.NetworkStatusBroadcast ) - { - updateSystem( ((module.decode.p25.message.ldu.lc.NetworkStatusBroadcast)ldu).getSystem() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case NETWORK_STATUS_BROADCAST_EXPLICIT: - if( ldu instanceof module.decode.p25.message.ldu.lc.NetworkStatusBroadcastExplicit ) - { - updateSystem( ((module.decode.p25.message.ldu.lc.NetworkStatusBroadcastExplicit)ldu).getSystem() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case PROTECTION_PARAMETER_BROADCAST: - if( ldu instanceof module.decode.p25.message.ldu.lc.ProtectionParameterBroadcast ) - { - module.decode.p25.message.ldu.lc.ProtectionParameterBroadcast ppb = - (module.decode.p25.message.ldu.lc.ProtectionParameterBroadcast)ldu; + if(mCurrentCallEvent.getDetails() == null) + { + mCurrentCallEvent.setDetails( + (gvcuser.isEncrypted() ? "ENCRYPTED " : "") + + (gvcuser.isEmergency() ? "EMERGENCY " : "")); - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .to( ppb.getTargetAddress() ) - .details( "ENCRYPTION: " + - ppb.getEncryption().name() + " KEY:" + - ppb.getEncryptionKey() ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case RFSS_STATUS_BROADCAST: - if( ldu instanceof module.decode.p25.message.ldu.lc.RFSSStatusBroadcast ) - { - module.decode.p25.message.ldu.lc.RFSSStatusBroadcast rfsssb = - (module.decode.p25.message.ldu.lc.RFSSStatusBroadcast)ldu; - - updateSystem( rfsssb.getSystem() ); + broadcast(mCurrentCallEvent); + } + } + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case MESSAGE_UPDATE: + if(ldu instanceof module.decode.p25.message.ldu.lc.MessageUpdate) + { + module.decode.p25.message.ldu.lc.MessageUpdate mu = + (module.decode.p25.message.ldu.lc.MessageUpdate) ldu; - String site = rfsssb.getRFSubsystemID() + "-" + - rfsssb.getSiteID(); + broadcast(new P25CallEvent.Builder(CallEventType.SDM) + .aliasList(getAliasList()) + .from(mu.getSourceAddress()) + .to(mu.getTargetAddress()) + .details("MSG: " + mu.getShortDataMessage()) + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case NETWORK_STATUS_BROADCAST: + if(ldu instanceof module.decode.p25.message.ldu.lc.NetworkStatusBroadcast) + { + updateSystem(((module.decode.p25.message.ldu.lc.NetworkStatusBroadcast) ldu).getSystem()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case NETWORK_STATUS_BROADCAST_EXPLICIT: + if(ldu instanceof module.decode.p25.message.ldu.lc.NetworkStatusBroadcastExplicit) + { + updateSystem(((module.decode.p25.message.ldu.lc.NetworkStatusBroadcastExplicit) ldu).getSystem()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case PROTECTION_PARAMETER_BROADCAST: + if(ldu instanceof module.decode.p25.message.ldu.lc.ProtectionParameterBroadcast) + { + module.decode.p25.message.ldu.lc.ProtectionParameterBroadcast ppb = + (module.decode.p25.message.ldu.lc.ProtectionParameterBroadcast) ldu; - updateSite( site ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case RFSS_STATUS_BROADCAST_EXPLICIT: - if( ldu instanceof module.decode.p25.message.ldu.lc.RFSSStatusBroadcastExplicit ) - { - module.decode.p25.message.ldu.lc.RFSSStatusBroadcastExplicit rfsssbe = - (module.decode.p25.message.ldu.lc.RFSSStatusBroadcastExplicit)ldu; - - String site = rfsssbe.getRFSubsystemID() + "-" + - rfsssbe.getSiteID(); + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .to(ppb.getTargetAddress()) + .details("ENCRYPTION: " + + ppb.getEncryption().name() + " KEY:" + + ppb.getEncryptionKey()) + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case RFSS_STATUS_BROADCAST: + if(ldu instanceof module.decode.p25.message.ldu.lc.RFSSStatusBroadcast) + { + module.decode.p25.message.ldu.lc.RFSSStatusBroadcast rfsssb = + (module.decode.p25.message.ldu.lc.RFSSStatusBroadcast) ldu; - updateSite( site ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case SECONDARY_CONTROL_CHANNEL_BROADCAST: - if( ldu instanceof module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcast ) - { - module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcast sccb = - (module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcast)ldu; - - String site = sccb.getRFSubsystemID() + "-" + - sccb.getSiteID(); + updateSystem(rfsssb.getSystem()); - updateSite( site ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: - if( ldu instanceof module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcastExplicit ) - { - module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcastExplicit sccb = - (module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcastExplicit)ldu; - - String site = sccb.getRFSubsystemID() + "-" + - sccb.getSiteID(); + String site = rfsssb.getRFSubsystemID() + "-" + + rfsssb.getSiteID(); - updateSite( site ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case STATUS_QUERY: - if( ldu instanceof module.decode.p25.message.ldu.lc.StatusQuery ) - { - module.decode.p25.message.ldu.lc.StatusQuery sq = - (module.decode.p25.message.ldu.lc.StatusQuery)ldu; + updateSite(site); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case RFSS_STATUS_BROADCAST_EXPLICIT: + if(ldu instanceof module.decode.p25.message.ldu.lc.RFSSStatusBroadcastExplicit) + { + module.decode.p25.message.ldu.lc.RFSSStatusBroadcastExplicit rfsssbe = + (module.decode.p25.message.ldu.lc.RFSSStatusBroadcastExplicit) ldu; - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .details( "STATUS QUERY" ) - .from( sq.getSourceAddress() ) - .to( sq.getTargetAddress() ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case STATUS_UPDATE: - if( ldu instanceof module.decode.p25.message.ldu.lc.StatusUpdate ) - { - module.decode.p25.message.ldu.lc.StatusUpdate su = - (module.decode.p25.message.ldu.lc.StatusUpdate)ldu; + String site = rfsssbe.getRFSubsystemID() + "-" + + rfsssbe.getSiteID(); - broadcast( new P25CallEvent.Builder( CallEventType.STATUS ) - .aliasList( getAliasList() ) - .details( "STATUS UNIT:" + su.getUnitStatus() + - " USER:" + su.getUserStatus() ) - .from( su.getSourceAddress() ) - .to( su.getTargetAddress() ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case SYSTEM_SERVICE_BROADCAST: + updateSite(site); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case SECONDARY_CONTROL_CHANNEL_BROADCAST: + if(ldu instanceof module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcast) + { + module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcast sccb = + (module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcast) ldu; + + String site = sccb.getRFSubsystemID() + "-" + + sccb.getSiteID(); + + updateSite(site); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT: + if(ldu instanceof module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcastExplicit) + { + module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcastExplicit sccb = + (module.decode.p25.message.ldu.lc.SecondaryControlChannelBroadcastExplicit) ldu; + + String site = sccb.getRFSubsystemID() + "-" + + sccb.getSiteID(); + + updateSite(site); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case STATUS_QUERY: + if(ldu instanceof module.decode.p25.message.ldu.lc.StatusQuery) + { + module.decode.p25.message.ldu.lc.StatusQuery sq = + (module.decode.p25.message.ldu.lc.StatusQuery) ldu; + + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .details("STATUS QUERY") + .from(sq.getSourceAddress()) + .to(sq.getTargetAddress()) + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case STATUS_UPDATE: + if(ldu instanceof module.decode.p25.message.ldu.lc.StatusUpdate) + { + module.decode.p25.message.ldu.lc.StatusUpdate su = + (module.decode.p25.message.ldu.lc.StatusUpdate) ldu; + + broadcast(new P25CallEvent.Builder(CallEventType.STATUS) + .aliasList(getAliasList()) + .details("STATUS UNIT:" + su.getUnitStatus() + + " USER:" + su.getUserStatus()) + .from(su.getSourceAddress()) + .to(su.getTargetAddress()) + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case SYSTEM_SERVICE_BROADCAST: /* This message doesn't provide anything we need for channel state */ - break; - case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - if( ldu instanceof module.decode.p25.message.ldu.lc.TelephoneInterconnectAnswerRequest ) - { - module.decode.p25.message.ldu.lc.TelephoneInterconnectAnswerRequest tiar = - (module.decode.p25.message.ldu.lc.TelephoneInterconnectAnswerRequest)ldu; + break; + case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: + if(ldu instanceof module.decode.p25.message.ldu.lc.TelephoneInterconnectAnswerRequest) + { + module.decode.p25.message.ldu.lc.TelephoneInterconnectAnswerRequest tiar = + (module.decode.p25.message.ldu.lc.TelephoneInterconnectAnswerRequest) ldu; - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .from( tiar.getTelephoneNumber() ) - .to( tiar.getTargetAddress() ) - .details( "TELEPHONE CALL ALERT" ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .from(tiar.getTelephoneNumber()) + .to(tiar.getTargetAddress()) + .details("TELEPHONE CALL ALERT") + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER: /* Indicates the current user of the current channel */ - if( mChannelType == ChannelType.STANDARD && - ldu instanceof TelephoneInterconnectVoiceChannelUser ) - { - TelephoneInterconnectVoiceChannelUser tivcu = - (TelephoneInterconnectVoiceChannelUser)ldu; + if(mChannelType == ChannelType.STANDARD && + ldu instanceof TelephoneInterconnectVoiceChannelUser) + { + TelephoneInterconnectVoiceChannelUser tivcu = + (TelephoneInterconnectVoiceChannelUser) ldu; - updateTo( tivcu.getAddress() ); + updateTo(tivcu.getAddress()); - if( mCurrentCallEvent.getCallEventType() != CallEventType.TELEPHONE_INTERCONNECT ) - { - mCurrentCallEvent.setCallEventType( CallEventType.TELEPHONE_INTERCONNECT ); - broadcast( mCurrentCallEvent ); - } + if(mCurrentCallEvent.getCallEventType() != CallEventType.TELEPHONE_INTERCONNECT) + { + mCurrentCallEvent.setCallEventType(CallEventType.TELEPHONE_INTERCONNECT); + broadcast(mCurrentCallEvent); + } - if( mCurrentCallEvent.getDetails() == null ) - { - mCurrentCallEvent.setDetails( - ( tivcu.isEncrypted() ? "ENCRYPTED " : "" ) + - ( tivcu.isEmergency() ? "EMERGENCY " : "" ) ); + if(mCurrentCallEvent.getDetails() == null) + { + mCurrentCallEvent.setDetails( + (tivcu.isEncrypted() ? "ENCRYPTED " : "") + + (tivcu.isEmergency() ? "EMERGENCY " : "")); - broadcast( mCurrentCallEvent ); - } - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case UNIT_AUTHENTICATION_COMMAND: - if( ldu instanceof module.decode.p25.message.ldu.lc.UnitAuthenticationCommand ) - { - module.decode.p25.message.ldu.lc.UnitAuthenticationCommand uac = - (module.decode.p25.message.ldu.lc.UnitAuthenticationCommand)ldu; + broadcast(mCurrentCallEvent); + } + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case UNIT_AUTHENTICATION_COMMAND: + if(ldu instanceof module.decode.p25.message.ldu.lc.UnitAuthenticationCommand) + { + module.decode.p25.message.ldu.lc.UnitAuthenticationCommand uac = + (module.decode.p25.message.ldu.lc.UnitAuthenticationCommand) ldu; - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .to( uac.getCompleteTargetAddress() ) - .details( "AUTHENTICATE" ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case UNIT_REGISTRATION_COMMAND: - if( ldu instanceof module.decode.p25.message.ldu.lc.UnitRegistrationCommand ) - { - module.decode.p25.message.ldu.lc.UnitRegistrationCommand urc = - (module.decode.p25.message.ldu.lc.UnitRegistrationCommand)ldu; + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .to(uac.getCompleteTargetAddress()) + .details("AUTHENTICATE") + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case UNIT_REGISTRATION_COMMAND: + if(ldu instanceof module.decode.p25.message.ldu.lc.UnitRegistrationCommand) + { + module.decode.p25.message.ldu.lc.UnitRegistrationCommand urc = + (module.decode.p25.message.ldu.lc.UnitRegistrationCommand) ldu; - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .to( urc.getCompleteTargetAddress() ) - .details( "REGISTER" ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case UNIT_TO_UNIT_ANSWER_REQUEST: - if( ldu instanceof module.decode.p25.message.ldu.lc.UnitToUnitAnswerRequest ) - { - module.decode.p25.message.ldu.lc.UnitToUnitAnswerRequest uuar = - (module.decode.p25.message.ldu.lc.UnitToUnitAnswerRequest)ldu; + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .to(urc.getCompleteTargetAddress()) + .details("REGISTER") + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case UNIT_TO_UNIT_ANSWER_REQUEST: + if(ldu instanceof module.decode.p25.message.ldu.lc.UnitToUnitAnswerRequest) + { + module.decode.p25.message.ldu.lc.UnitToUnitAnswerRequest uuar = + (module.decode.p25.message.ldu.lc.UnitToUnitAnswerRequest) ldu; - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .from( uuar.getSourceAddress() ) - .to( uuar.getTargetAddress() ) - .details( "UNIT TO UNIT CALL ALERT" ) - .build() ); - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - case UNIT_TO_UNIT_VOICE_CHANNEL_USER: - if( mChannelType == ChannelType.STANDARD && - ldu instanceof UnitToUnitVoiceChannelUser ) - { - UnitToUnitVoiceChannelUser uuvcu = - (UnitToUnitVoiceChannelUser)ldu; + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .from(uuar.getSourceAddress()) + .to(uuar.getTargetAddress()) + .details("UNIT TO UNIT CALL ALERT") + .build()); + } + else + { + logAlternateVendorMessage(ldu); + } + break; + case UNIT_TO_UNIT_VOICE_CHANNEL_USER: + if(mChannelType == ChannelType.STANDARD && + ldu instanceof UnitToUnitVoiceChannelUser) + { + UnitToUnitVoiceChannelUser uuvcu = + (UnitToUnitVoiceChannelUser) ldu; - updateFrom( uuvcu.getSourceAddress() ); - - updateTo( uuvcu.getTargetAddress() ); - - if( mCurrentCallEvent.getCallEventType() != CallEventType.UNIT_TO_UNIT_CALL ) - { - mCurrentCallEvent.setCallEventType( CallEventType.UNIT_TO_UNIT_CALL ); - broadcast( mCurrentCallEvent ); - } + updateFrom(uuvcu.getSourceAddress()); - if( mCurrentCallEvent.getDetails() == null ) - { - mCurrentCallEvent.setDetails( - ( uuvcu.isEncrypted() ? "ENCRYPTED " : "" ) + - ( uuvcu.isEmergency() ? "EMERGENCY " : "" ) ); + updateTo(uuvcu.getTargetAddress()); - broadcast( mCurrentCallEvent ); - } - } - else - { - logAlternateVendorMessage( ldu ); - } - break; - default: - break; - } - } - } - - /** - * Process a Trunking Signalling Block message - */ - private void processTSBK( TSBKMessage tsbk ) - { + if(mCurrentCallEvent.getCallEventType() != CallEventType.UNIT_TO_UNIT_CALL) + { + mCurrentCallEvent.setCallEventType(CallEventType.UNIT_TO_UNIT_CALL); + broadcast(mCurrentCallEvent); + } + + if(mCurrentCallEvent.getDetails() == null) + { + mCurrentCallEvent.setDetails( + (uuvcu.isEncrypted() ? "ENCRYPTED " : "") + + (uuvcu.isEmergency() ? "EMERGENCY " : "")); + + broadcast(mCurrentCallEvent); + } + } + else + { + logAlternateVendorMessage(ldu); + } + break; + default: + break; + } + } + } + + /** + * Process a Trunking Signalling Block message + */ + private void processTSBK(TSBKMessage tsbk) + { /* Trunking Signalling Block Messages - indicates Control Channel */ - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) ); + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); - if( tsbk.getVendor() == Vendor.STANDARD ) - { - switch( tsbk.getOpcode() ) - { - case ADJACENT_STATUS_BROADCAST: - if( tsbk instanceof AdjacentStatusBroadcast ) - { - IAdjacentSite ias = (IAdjacentSite)tsbk; - - mNeighborMap.put( ias.getUniqueID(), ias ); - - updateSystem( ias.getSystemID() ); - } - break; - case ACKNOWLEDGE_RESPONSE: - processTSBKResponse( tsbk ); - break; - case AUTHENTICATION_COMMAND: - processTSBKCommand( tsbk ); - break; - case CALL_ALERT: - processTSBKPage( tsbk ); - break; - case DENY_RESPONSE: - processTSBKResponse( tsbk ); - break; - case EXTENDED_FUNCTION_COMMAND: - processTSBKCommand( tsbk ); - break; - case GROUP_AFFILIATION_QUERY: - processTSBKQuery( tsbk ); - break; - case GROUP_AFFILIATION_RESPONSE: - processTSBKResponse( tsbk ); - break; - case GROUP_DATA_CHANNEL_ANNOUNCEMENT: - case GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: - processTSBKDataChannelAnnouncement( tsbk); - break; - case GROUP_DATA_CHANNEL_GRANT: - case GROUP_VOICE_CHANNEL_GRANT: - case GROUP_VOICE_CHANNEL_GRANT_UPDATE: - case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: - case INDIVIDUAL_DATA_CHANNEL_GRANT: - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: - processTSBKChannelGrant( tsbk ); - break; - case IDENTIFIER_UPDATE_NON_VUHF: - case IDENTIFIER_UPDATE_VHF_UHF_BANDS: - IdentifierUpdate iu = (IdentifierUpdate)tsbk; - - if( !mBands.containsKey( iu.getIdentifier() ) ) - { - mBands.put( iu.getIdentifier(), iu ); - } - break; - case LOCATION_REGISTRATION_RESPONSE: - case UNIT_DEREGISTRATION_ACKNOWLEDGE: - processTSBKResponse( tsbk ); - break; - case MESSAGE_UPDATE: - processTSBKMessage( tsbk ); - break; - case NETWORK_STATUS_BROADCAST: - mNetworkStatus = (module.decode.p25.message.tsbk.osp - .control.NetworkStatusBroadcast)tsbk; - break; - case PROTECTION_PARAMETER_UPDATE: - processTSBKResponse( tsbk ); - break; - case QUEUED_RESPONSE: - processTSBKResponse( tsbk ); - break; - case RADIO_UNIT_MONITOR_COMMAND: - processTSBKCommand( tsbk ); - break; - case RFSS_STATUS_BROADCAST: - processTSBKRFSSStatus( (RFSSStatusBroadcast)tsbk ); - break; - case ROAMING_ADDRESS_COMMAND: - processTSBKCommand( tsbk ); - break; - case SECONDARY_CONTROL_CHANNEL_BROADCAST: - module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast sccb = - (module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast)tsbk; - - if( sccb.getDownlinkFrequency1() > 0 ) - { - mSecondaryControlChannels.add( sccb ); - } - break; - case SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: - mSNDCPDataChannel = (SNDCPDataChannelAnnouncementExplicit)tsbk; - break; - case SNDCP_DATA_CHANNEL_GRANT: - processTSBKChannelGrant( tsbk ); - break; - case STATUS_QUERY: - processTSBKQuery( tsbk ); - break; - case STATUS_UPDATE: - processTSBKResponse( tsbk ); - break; - case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: - case UNIT_TO_UNIT_ANSWER_REQUEST: - processTSBKPage( tsbk ); - break; - case UNIT_REGISTRATION_COMMAND: - processTSBKCommand( tsbk ); - break; - case UNIT_REGISTRATION_RESPONSE: - processTSBKResponse( tsbk ); - break; - default: - break; - } - } - else if( tsbk.getVendor() == Vendor.MOTOROLA ) - { - processMotorolaTSBK((MotorolaTSBKMessage)tsbk); - } - } + if(tsbk.getVendor() == Vendor.STANDARD) + { + switch(tsbk.getOpcode()) + { + case ADJACENT_STATUS_BROADCAST: + if(tsbk instanceof AdjacentStatusBroadcast) + { + IAdjacentSite ias = (IAdjacentSite) tsbk; - /** - * Process a Packet Data Unit message - */ - private void processPDU( PDUMessage pdu ) - { - if( pdu instanceof PacketData || pdu instanceof PDUTypeUnknown ) - { - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) ); - } - else if( pdu instanceof PDUConfirmedMessage ) - { - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.DATA ) ); - - PDUConfirmedMessage pduc = (PDUConfirmedMessage)pdu; + mNeighborMap.put(ias.getUniqueID(), ias); - switch( pduc.getPDUType() ) - { - case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT: - SNDCPActivateTDSContextAccept satca = - (SNDCPActivateTDSContextAccept)pduc; - - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( "ACTIVATE SNDCP USE IP:" + satca.getIPAddress() ) - .frequency( mCurrentChannelFrequency ) - .to( satca.getLogicalLinkID() ) - .build() ); - break; - case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT: - SNDCPActivateTDSContextReject satcr = - (SNDCPActivateTDSContextReject)pduc; - - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( "REJECT: SNDCP CONTEXT ACTIVATION " - + "REASON:" + satcr.getReason().getLabel() ) - .frequency( mCurrentChannelFrequency ) - .to( satcr.getLogicalLinkID() ) - .build() ); - break; - case SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST: - SNDCPActivateTDSContextRequest satcreq = - (SNDCPActivateTDSContextRequest)pduc; - - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( "REQUEST SNDCP USE IP:" + satcreq.getIPAddress() ) - .frequency( mCurrentChannelFrequency ) - .from( satcreq.getLogicalLinkID() ) - .build() ); - break; - case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: - SNDCPDeactivateTDSContext sdtca = - (SNDCPDeactivateTDSContext)pduc; + updateSystem(ias.getSystemID()); + } + break; + case ACKNOWLEDGE_RESPONSE: + processTSBKResponse(tsbk); + break; + case AUTHENTICATION_COMMAND: + processTSBKCommand(tsbk); + break; + case CALL_ALERT: + processTSBKPage(tsbk); + break; + case DENY_RESPONSE: + processTSBKResponse(tsbk); + break; + case EXTENDED_FUNCTION_COMMAND: + processTSBKCommand(tsbk); + break; + case GROUP_AFFILIATION_QUERY: + processTSBKQuery(tsbk); + break; + case GROUP_AFFILIATION_RESPONSE: + processTSBKResponse(tsbk); + break; + case GROUP_DATA_CHANNEL_ANNOUNCEMENT: + case GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: + processTSBKDataChannelAnnouncement(tsbk); + break; + case GROUP_DATA_CHANNEL_GRANT: + case GROUP_VOICE_CHANNEL_GRANT: + case GROUP_VOICE_CHANNEL_GRANT_UPDATE: + case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + case INDIVIDUAL_DATA_CHANNEL_GRANT: + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + processTSBKChannelGrant(tsbk); + break; + case IDENTIFIER_UPDATE_NON_VUHF: + case IDENTIFIER_UPDATE_VHF_UHF_BANDS: + IdentifierUpdate iu = (IdentifierUpdate) tsbk; - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( "ACCEPT DEACTIVATE SNDCP CONTEXT" ) - .frequency( mCurrentChannelFrequency ) - .from( sdtca.getLogicalLinkID() ) - .build() ); - break; - case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: - SNDCPDeactivateTDSContext sdtcreq = - (SNDCPDeactivateTDSContext)pduc; - - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( "REQUEST DEACTIVATE SNDCP CONTEXT" ) - .frequency( mCurrentChannelFrequency ) - .from( sdtcreq.getLogicalLinkID() ) - .build() ); - break; - case SNDCP_RF_CONFIRMED_DATA: - SNDCPUserData sud = (SNDCPUserData)pduc; - - StringBuilder sbFrom = new StringBuilder(); - StringBuilder sbTo = new StringBuilder(); - - sbFrom.append( sud.getSourceIPAddress() ); - sbTo.append( sud.getDestinationIPAddress() ); - - if( sud.getIPProtocol() == IPProtocol.UDP ) - { - sbFrom.append( ":" ); - sbFrom.append( sud.getUDPSourcePort() ); - sbTo.append( ":" ); - sbTo.append( sud.getUDPDestinationPort() ); - } + if(!mBands.containsKey(iu.getIdentifier())) + { + mBands.put(iu.getIdentifier(), iu); + } + break; + case LOCATION_REGISTRATION_RESPONSE: + case UNIT_DEREGISTRATION_ACKNOWLEDGE: + processTSBKResponse(tsbk); + break; + case MESSAGE_UPDATE: + processTSBKMessage(tsbk); + break; + case NETWORK_STATUS_BROADCAST: + mNetworkStatus = (module.decode.p25.message.tsbk.osp + .control.NetworkStatusBroadcast) tsbk; + break; + case PROTECTION_PARAMETER_UPDATE: + processTSBKResponse(tsbk); + break; + case QUEUED_RESPONSE: + processTSBKResponse(tsbk); + break; + case RADIO_UNIT_MONITOR_COMMAND: + processTSBKCommand(tsbk); + break; + case RFSS_STATUS_BROADCAST: + processTSBKRFSSStatus((RFSSStatusBroadcast) tsbk); + break; + case ROAMING_ADDRESS_COMMAND: + processTSBKCommand(tsbk); + break; + case SECONDARY_CONTROL_CHANNEL_BROADCAST: + module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast sccb = + (module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast) tsbk; - broadcast( new DecoderStateEvent( this, Event.START, State.DATA ) ); - - broadcast( new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( mCurrentChannel ) - .details( "DATA: " + sud.getPayload() + - " RADIO IP:" + sbTo.toString() ) - .frequency( mCurrentChannelFrequency ) - .from( sbFrom.toString() ) - .to( pduc.getLogicalLinkID() ) - .build() ); - break; - case SNDCP_RF_UNCONFIRMED_DATA: - break; - default: + if(sccb.getDownlinkFrequency1() > 0) + { + mSecondaryControlChannels.add(sccb); + } + break; + case SNDCP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: + mSNDCPDataChannel = (SNDCPDataChannelAnnouncementExplicit) tsbk; + break; + case SNDCP_DATA_CHANNEL_GRANT: + processTSBKChannelGrant(tsbk); + break; + case STATUS_QUERY: + processTSBKQuery(tsbk); + break; + case STATUS_UPDATE: + processTSBKResponse(tsbk); + break; + case TELEPHONE_INTERCONNECT_ANSWER_REQUEST: + case UNIT_TO_UNIT_ANSWER_REQUEST: + processTSBKPage(tsbk); + break; + case UNIT_REGISTRATION_COMMAND: + processTSBKCommand(tsbk); + break; + case UNIT_REGISTRATION_RESPONSE: + processTSBKResponse(tsbk); + break; + default: + break; + } + } + else if(tsbk.getVendor() == Vendor.MOTOROLA) + { + processMotorolaTSBK((MotorolaTSBKMessage) tsbk); + } + } + + /** + * Process a Packet Data Unit message + */ + private void processPDU(PDUMessage pdu) + { + if(pdu instanceof PacketData || pdu instanceof PDUTypeUnknown) + { + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); + } + else if(pdu instanceof PDUConfirmedMessage) + { + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); + + PDUConfirmedMessage pduc = (PDUConfirmedMessage) pdu; + + switch(pduc.getPDUType()) + { + case SNDCP_ACTIVATE_TDS_CONTEXT_ACCEPT: + SNDCPActivateTDSContextAccept satca = + (SNDCPActivateTDSContextAccept) pduc; + + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details("ACTIVATE SNDCP USE IP:" + satca.getIPAddress()) + .frequency(mCurrentChannelFrequency) + .to(satca.getLogicalLinkID()) + .build()); + break; + case SNDCP_ACTIVATE_TDS_CONTEXT_REJECT: + SNDCPActivateTDSContextReject satcr = + (SNDCPActivateTDSContextReject) pduc; + + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details("REJECT: SNDCP CONTEXT ACTIVATION " + + "REASON:" + satcr.getReason().getLabel()) + .frequency(mCurrentChannelFrequency) + .to(satcr.getLogicalLinkID()) + .build()); + break; + case SNDCP_ACTIVATE_TDS_CONTEXT_REQUEST: + SNDCPActivateTDSContextRequest satcreq = + (SNDCPActivateTDSContextRequest) pduc; + + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details("REQUEST SNDCP USE IP:" + satcreq.getIPAddress()) + .frequency(mCurrentChannelFrequency) + .from(satcreq.getLogicalLinkID()) + .build()); + break; + case SNDCP_DEACTIVATE_TDS_CONTEXT_ACCEPT: + SNDCPDeactivateTDSContext sdtca = + (SNDCPDeactivateTDSContext) pduc; + + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details("ACCEPT DEACTIVATE SNDCP CONTEXT") + .frequency(mCurrentChannelFrequency) + .from(sdtca.getLogicalLinkID()) + .build()); + break; + case SNDCP_DEACTIVATE_TDS_CONTEXT_REQUEST: + SNDCPDeactivateTDSContext sdtcreq = + (SNDCPDeactivateTDSContext) pduc; + + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details("REQUEST DEACTIVATE SNDCP CONTEXT") + .frequency(mCurrentChannelFrequency) + .from(sdtcreq.getLogicalLinkID()) + .build()); + break; + case SNDCP_RF_CONFIRMED_DATA: + SNDCPUserData sud = (SNDCPUserData) pduc; + + StringBuilder sbFrom = new StringBuilder(); + StringBuilder sbTo = new StringBuilder(); + + sbFrom.append(sud.getSourceIPAddress()); + sbTo.append(sud.getDestinationIPAddress()); + + if(sud.getIPProtocol() == IPProtocol.UDP) + { + sbFrom.append(":"); + sbFrom.append(sud.getUDPSourcePort()); + sbTo.append(":"); + sbTo.append(sud.getUDPDestinationPort()); + } + + broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); + + broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL) + .aliasList(getAliasList()) + .channel(mCurrentChannel) + .details("DATA: " + sud.getPayload() + + " RADIO IP:" + sbTo.toString()) + .frequency(mCurrentChannelFrequency) + .from(sbFrom.toString()) + .to(pduc.getLogicalLinkID()) + .build()); + break; + case SNDCP_RF_UNCONFIRMED_DATA: + break; + default: // mLog.debug( "PDUC - Unrecognized Message: " + pduc.toString() ); - break; - } - } - else - { + break; + } + } + else + { /* These are alternate trunking control blocks in PDU format */ - broadcast( new DecoderStateEvent( this, Event.CONTINUATION, State.CONTROL ) ); - - switch( pdu.getOpcode() ) - { - case GROUP_DATA_CHANNEL_GRANT: - case GROUP_VOICE_CHANNEL_GRANT: - case INDIVIDUAL_DATA_CHANNEL_GRANT: - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: - processPDUChannelGrant( pdu ); - break; - case ADJACENT_STATUS_BROADCAST: - if( pdu instanceof AdjacentStatusBroadcastExtended ) - { - IAdjacentSite ias = (IAdjacentSite)pdu; - - mNeighborMap.put( ias.getUniqueID(), ias ); - - updateSystem( ias.getSystemID() ); - } - break; - case CALL_ALERT: - if( pdu instanceof CallAlertExtended ) - { - CallAlertExtended ca = (CallAlertExtended)pdu; - - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .from( ca.getWACN() + "-" + ca.getSystemID() + "-" + - ca.getSourceID() ) - .to( ca.getTargetAddress() ) - .build() ); - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case GROUP_AFFILIATION_QUERY: - if( pdu instanceof GroupAffiliationQueryExtended ) - { - GroupAffiliationQueryExtended gaqe = - (GroupAffiliationQueryExtended)pdu; - - if( mLastQueryEventID == null || !gaqe.getTargetAddress() - .contentEquals( mLastQueryEventID ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .details( "GROUP AFFILIATION" ) - .from( gaqe.getWACN() + "-" + gaqe.getSystemID() + - "-" + gaqe.getSourceID() ) - .to( gaqe.getTargetAddress() ) - .build() ); + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); - mLastQueryEventID = gaqe.getToID(); - } - } - break; - case GROUP_AFFILIATION_RESPONSE: - if( pdu instanceof GroupAffiliationResponseExtended ) - { - GroupAffiliationResponseExtended gar = - (GroupAffiliationResponseExtended)pdu; - - if( mLastResponseEventID == null || !gar.getTargetAddress() - .contentEquals( mLastResponseEventID ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( getAliasList() ) - .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(); - } - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case MESSAGE_UPDATE: - if( pdu instanceof MessageUpdateExtended ) - { - MessageUpdateExtended mu = (MessageUpdateExtended)pdu; - - broadcast( new P25CallEvent.Builder( CallEventType.SDM ) - .aliasList( getAliasList() ) - .details( "MESSAGE: " + mu.getMessage() ) - .from( mu.getSourceWACN() + "-" + mu.getSourceSystemID() + - "-" + mu.getSourceID() ) - .to( mu.getTargetAddress() ) - .build() ); - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case NETWORK_STATUS_BROADCAST: - if( pdu instanceof NetworkStatusBroadcastExtended ) - { - mNetworkStatusExtended = (NetworkStatusBroadcastExtended)pdu; - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case PROTECTION_PARAMETER_BROADCAST: - if( pdu instanceof ProtectionParameterBroadcast ) - { - mProtectionParameterBroadcast = - (ProtectionParameterBroadcast)pdu; - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case RFSS_STATUS_BROADCAST: - if( pdu instanceof RFSSStatusBroadcastExtended ) - { - mRFSSStatusMessageExtended = (RFSSStatusBroadcastExtended)pdu; - - updateNAC( mRFSSStatusMessageExtended.getNAC() ); - updateSystem( mRFSSStatusMessageExtended.getSystemID() ); - updateSite( mRFSSStatusMessageExtended.getRFSubsystemID() + - "-" + mRFSSStatusMessageExtended.getSiteID() ); - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case ROAMING_ADDRESS_UPDATE: - if( pdu instanceof RoamingAddressUpdateExtended ) - { - RoamingAddressUpdateExtended raue = - (RoamingAddressUpdateExtended)pdu; + switch(pdu.getOpcode()) + { + case GROUP_DATA_CHANNEL_GRANT: + case GROUP_VOICE_CHANNEL_GRANT: + case INDIVIDUAL_DATA_CHANNEL_GRANT: + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + processPDUChannelGrant(pdu); + break; + case ADJACENT_STATUS_BROADCAST: + if(pdu instanceof AdjacentStatusBroadcastExtended) + { + IAdjacentSite ias = (IAdjacentSite) pdu; - StringBuilder sb = new StringBuilder(); - sb.append( "ROAMING ADDRESS STACK A:" ); - sb.append( raue.getWACNA() + "-" + raue.getSystemIDA() ); - - if( raue.isFormat2() ) - { - sb.append( " B:" ); - sb.append( raue.getWACNB() + "-" + raue.getSystemIDB() ); - sb.append( " C:" ); - sb.append( raue.getWACNC() + "-" + raue.getSystemIDC() ); - sb.append( " D:" ); - sb.append( raue.getWACND() + "-" + raue.getSystemIDD() ); - } - - if( raue.isFormat3() ) - { - sb.append( " E:" ); - sb.append( raue.getWACNE() + "-" + raue.getSystemIDE() ); - sb.append( " F:" ); - sb.append( raue.getWACNF() + "-" + raue.getSystemIDF() ); - sb.append( " G:" ); - sb.append( raue.getWACNG() + "-" + raue.getSystemIDG() ); - } - - broadcast( new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( getAliasList() ) - .details( sb.toString() ) - .from( raue.getSourceID() ) - .to( raue.getTargetAddress() ) - .build() ); - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case STATUS_QUERY: - if( pdu instanceof StatusQueryExtended ) - { - StatusQueryExtended sq = (StatusQueryExtended)pdu; - - if( mLastQueryEventID == null || !sq.getTargetAddress() - .contentEquals( mLastQueryEventID ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .details( "STATUS QUERY" ) - .from( sq.getSourceWACN() + "-" + - sq.getSourceSystemID() + "-" + - sq.getSourceID() ) - .to( sq.getTargetAddress() ) - .build() ); + mNeighborMap.put(ias.getUniqueID(), ias); - mLastQueryEventID = sq.getToID(); - } - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case STATUS_UPDATE: - if( pdu instanceof StatusUpdateExtended ) - { - StatusUpdateExtended su = (StatusUpdateExtended)pdu; + updateSystem(ias.getSystemID()); + } + break; + case CALL_ALERT: + if(pdu instanceof CallAlertExtended) + { + CallAlertExtended ca = (CallAlertExtended) pdu; - if( mLastResponseEventID == null || !mLastResponseEventID - .contentEquals( su.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( getAliasList() ) - .details( "STATUS USER: " + su.getUserStatus() + - " UNIT: " + su.getUnitStatus() ) - .from( su.getSourceWACN() + "-" + - su.getSourceSystemID() + "-" + - su.getSourceID() ) - .to( su.getTargetAddress() ) - .build() ); - - mLastResponseEventID = su.getTargetAddress(); - } - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case UNIT_REGISTRATION_RESPONSE: - if( pdu instanceof UnitRegistrationResponseExtended ) - { - UnitRegistrationResponseExtended urr = - (UnitRegistrationResponseExtended)pdu; + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .from(ca.getWACN() + "-" + ca.getSystemID() + "-" + + ca.getSourceID()) + .to(ca.getTargetAddress()) + .build()); + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case GROUP_AFFILIATION_QUERY: + if(pdu instanceof GroupAffiliationQueryExtended) + { + GroupAffiliationQueryExtended gaqe = + (GroupAffiliationQueryExtended) pdu; - if( urr.getResponse() == Response.ACCEPT ) - { - mRegistrations.put( urr.getAssignedSourceAddress(), - System.currentTimeMillis() ); - } - - if( mLastRegistrationEventID == null || !mLastRegistrationEventID - .contentEquals( urr.getAssignedSourceAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.REGISTER ) - .aliasList( getAliasList() ) - .details( "REGISTRATION:" + urr.getResponse().name() + - " FOR EXTERNAL SYSTEM ADDRESS: " + - urr.getWACN() + "-" + - urr.getSystemID() + "-" + - urr.getSourceAddress() + - " SOURCE ID: " + urr.getSourceID() ) - .from( urr.getAssignedSourceAddress() ) - .build() ); - - mLastRegistrationEventID = urr.getAssignedSourceAddress(); - } - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case UNIT_TO_UNIT_ANSWER_REQUEST: - if( pdu instanceof UnitToUnitAnswerRequestExplicit ) - { - UnitToUnitAnswerRequestExplicit utuare = - (UnitToUnitAnswerRequestExplicit)pdu; + if(mLastQueryEventID == null || !gaqe.getTargetAddress() + .contentEquals(mLastQueryEventID)) + { + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .details("GROUP AFFILIATION") + .from(gaqe.getWACN() + "-" + gaqe.getSystemID() + + "-" + gaqe.getSourceID()) + .to(gaqe.getTargetAddress()) + .build()); - if( mLastPageEventID == null || !mLastPageEventID - .contentEquals( utuare.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .details( ( utuare.isEmergency() ? "EMERGENCY" : "" ) ) - .from( utuare.getWACN() + "-" + - utuare.getSystemID() + "-" + - utuare.getSourceID() ) - .to( utuare.getTargetAddress() ) - .build() ); - - mLastPageEventID = utuare.getTargetAddress(); - } - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - default: - break; - } - } - } - - private void processPDUChannelGrant( PDUMessage pdu ) - { - String channel = null; - String from = null; - String to = null; - - switch( pdu.getOpcode() ) - { - case GROUP_DATA_CHANNEL_GRANT: - if( pdu instanceof GroupDataChannelGrantExtended ) - { - GroupDataChannelGrantExtended gdcge = - (GroupDataChannelGrantExtended)pdu; + mLastQueryEventID = gaqe.getToID(); + } + } + break; + case GROUP_AFFILIATION_RESPONSE: + if(pdu instanceof GroupAffiliationResponseExtended) + { + GroupAffiliationResponseExtended gar = + (GroupAffiliationResponseExtended) pdu; - channel = gdcge.getTransmitChannel(); - from = gdcge.getSourceAddress(); - to = gdcge.getGroupAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent callEvent = new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( gdcge.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gdcge.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gdcge.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( callEvent ); - broadcast( callEvent ); - } - } - else - { - logAlternateVendorMessage( pdu ); - } - - if( !mIgnoreDataCalls ) - { - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - } - break; - case GROUP_VOICE_CHANNEL_GRANT: - if( pdu instanceof GroupVoiceChannelGrantExplicit ) - { - GroupVoiceChannelGrantExplicit gvcge = - (GroupVoiceChannelGrantExplicit)pdu; + if(mLastResponseEventID == null || !gar.getTargetAddress() + .contentEquals(mLastResponseEventID)) + { + broadcast(new P25CallEvent.Builder(CallEventType.RESPONSE) + .aliasList(getAliasList()) + .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()); - channel = gvcge.getTransmitChannel(); - from = gvcge.getSourceAddress(); - to = gvcge.getGroupAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent callEvent = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( gvcge.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcge.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcge.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( callEvent ); - broadcast( callEvent ); - } - - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case INDIVIDUAL_DATA_CHANNEL_GRANT: - if( pdu instanceof IndividualDataChannelGrantExtended ) - { - IndividualDataChannelGrantExtended idcge = - (IndividualDataChannelGrantExtended)pdu; - - channel = idcge.getTransmitChannel(); - from = idcge.getSourceWACN() + "-" + - idcge.getSourceSystemID() + "-" + - idcge.getSourceAddress(); - to = idcge.getTargetAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent callEvent = new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( idcge.isEncrypted() ? "ENCRYPTED" : "" ) + - ( idcge.isEmergency() ? " EMERGENCY" : "") ) - .frequency( idcge.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( callEvent ); - broadcast( callEvent ); - } - - if( !mIgnoreDataCalls ) - { - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - } - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - TelephoneInterconnectChannelGrantExplicit ticge = - (TelephoneInterconnectChannelGrantExplicit)pdu; + mLastResponseEventID = gar.getTargetAddress(); + } + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case MESSAGE_UPDATE: + if(pdu instanceof MessageUpdateExtended) + { + MessageUpdateExtended mu = (MessageUpdateExtended) pdu; - channel = ticge.getTransmitChannel(); - - //We don't know if the subscriber is calling or being called, so - //we use the same address in both from/to fields - from = ticge.getAddress(); - to = ticge.getAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent callEvent = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( ticge.isEncrypted() ? "ENCRYPTED" : "" ) + - ( ticge.isEmergency() ? " EMERGENCY" : "") + - " CALL TIMER:" + ticge.getCallTimer() ) - .frequency( ticge.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( callEvent ); - broadcast( callEvent ); - } - - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - break; - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - if( pdu instanceof UnitToUnitVoiceChannelGrantExtended ) - { - UnitToUnitVoiceChannelGrantExtended uuvcge = - (UnitToUnitVoiceChannelGrantExtended)pdu; - - channel = uuvcge.getTransmitChannel(); - from = uuvcge.getSourceWACN() + "-" + - uuvcge.getSourceSystemID() + "-" + - uuvcge.getSourceID(); - to = uuvcge.getTargetAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent callEvent = new P25CallEvent.Builder( CallEventType.UNIT_TO_UNIT_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( uuvcge.isEncrypted() ? "ENCRYPTED" : "" ) + - ( uuvcge.isEmergency() ? " EMERGENCY" : "") ) - .frequency( uuvcge.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( callEvent ); - broadcast( callEvent ); - } - - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: - if( pdu instanceof UnitToUnitVoiceChannelGrantUpdateExtended ) - { - UnitToUnitVoiceChannelGrantUpdateExtended uuvcgue = - (UnitToUnitVoiceChannelGrantUpdateExtended)pdu; + broadcast(new P25CallEvent.Builder(CallEventType.SDM) + .aliasList(getAliasList()) + .details("MESSAGE: " + mu.getMessage()) + .from(mu.getSourceWACN() + "-" + mu.getSourceSystemID() + + "-" + mu.getSourceID()) + .to(mu.getTargetAddress()) + .build()); + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case NETWORK_STATUS_BROADCAST: + if(pdu instanceof NetworkStatusBroadcastExtended) + { + mNetworkStatusExtended = (NetworkStatusBroadcastExtended) pdu; + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case PROTECTION_PARAMETER_BROADCAST: + if(pdu instanceof ProtectionParameterBroadcast) + { + mProtectionParameterBroadcast = + (ProtectionParameterBroadcast) pdu; + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case RFSS_STATUS_BROADCAST: + if(pdu instanceof RFSSStatusBroadcastExtended) + { + mRFSSStatusMessageExtended = (RFSSStatusBroadcastExtended) pdu; - channel = uuvcgue.getTransmitChannel(); - from = uuvcgue.getSourceWACN() + "-" + - uuvcgue.getSourceSystemID() + "-" + - uuvcgue.getSourceID(); - to = uuvcgue.getTargetAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent callEvent = new P25CallEvent.Builder( CallEventType.UNIT_TO_UNIT_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( uuvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + - ( uuvcgue.isEmergency() ? " EMERGENCY" : "") ) - .frequency( uuvcgue.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( callEvent ); - broadcast( callEvent ); - } - - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - - } - else - { - logAlternateVendorMessage( pdu ); - } - break; - default: - break; - } - } - - private void processTSBKCommand( TSBKMessage message ) - { - switch( message.getOpcode() ) - { - case AUTHENTICATION_COMMAND: - AuthenticationCommand ac = (AuthenticationCommand)message; - - if( mLastCommandEventID == null || !mLastCommandEventID - .contentEquals( ac.getFullTargetID() )) - { - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .details( "AUTHENTICATE" ) - .to( ac.getWACN() + "-" + ac.getSystemID() + "-" + - ac.getTargetID() ) - .build() ); - - mLastCommandEventID = ac.getFullTargetID(); - } - break; - case EXTENDED_FUNCTION_COMMAND: - ExtendedFunctionCommand efc = (ExtendedFunctionCommand)message; + updateNAC(mRFSSStatusMessageExtended.getNAC()); + updateSystem(mRFSSStatusMessageExtended.getSystemID()); + updateSite(mRFSSStatusMessageExtended.getRFSubsystemID() + + "-" + mRFSSStatusMessageExtended.getSiteID()); + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case ROAMING_ADDRESS_UPDATE: + if(pdu instanceof RoamingAddressUpdateExtended) + { + RoamingAddressUpdateExtended raue = + (RoamingAddressUpdateExtended) pdu; - if( mLastCommandEventID == null || !mLastCommandEventID - .contentEquals( efc.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.FUNCTION ) - .aliasList( getAliasList() ) - .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; - - if( mLastCommandEventID == null || !mLastCommandEventID - .contentEquals( rumc.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .details( "RADIO UNIT MONITOR" ) - .from( rumc.getSourceAddress() ) - .to( rumc.getTargetAddress() ) - .build() ); - - mLastCommandEventID = rumc.getTargetAddress(); - } - break; - case ROAMING_ADDRESS_COMMAND: - RoamingAddressCommand rac = (RoamingAddressCommand)message; - - if( mLastCommandEventID == null || !mLastCommandEventID - .contentEquals( rac.getTargetID() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .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; - - if( mLastCommandEventID == null || !mLastCommandEventID - .contentEquals( urc.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.COMMAND ) - .aliasList( getAliasList() ) - .details( "REGISTER" ) - .from( urc.getSourceAddress() ) - .to( urc.getTargetAddress() ) - .build() ); - - mLastCommandEventID = urc.getTargetAddress(); - } - break; - default: - break; - } - } - - private void processTSBKMessage( TSBKMessage message ) - { - MessageUpdate mu = (MessageUpdate)message; - - broadcast( new P25CallEvent.Builder( CallEventType.SDM ) - .aliasList( getAliasList() ) - .details( "MESSAGE: " + mu.getMessage() ) - .from( mu.getSourceAddress() ) - .to( mu.getTargetAddress() ) - .build() ); - } - - private void processTSBKQuery( TSBKMessage message ) - { - switch( message.getOpcode() ) - { - case GROUP_AFFILIATION_QUERY: - GroupAffiliationQuery gaq = (GroupAffiliationQuery)message; + StringBuilder sb = new StringBuilder(); + sb.append("ROAMING ADDRESS STACK A:"); + sb.append(raue.getWACNA() + "-" + raue.getSystemIDA()); - if( mLastQueryEventID == null || !mLastQueryEventID - .contentEquals( gaq.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .details( "GROUP AFFILIATION" ) - .from( gaq.getSourceAddress() ) - .to( gaq.getTargetAddress() ) - .build() ); - } - break; - case STATUS_QUERY: - StatusQuery sq = (StatusQuery)message; - - if( mLastQueryEventID == null || !mLastQueryEventID - .contentEquals( sq.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.QUERY ) - .aliasList( getAliasList() ) - .details( "STATUS QUERY" ) - .from( sq.getSourceAddress() ) - .to( sq.getTargetAddress() ) - .build() ); - } - break; - default: - break; - } - } - - private void processTSBKResponse( TSBKMessage message ) - { - switch( message.getOpcode() ) - { - case ACKNOWLEDGE_RESPONSE: - AcknowledgeResponse ar = (AcknowledgeResponse)message; - - if( mLastResponseEventID == null || !ar.getTargetAddress() - .contentEquals( mLastResponseEventID ) ) - { - String to = ar.getTargetAddress(); - - if( ar.hasAdditionalInformation() && ar.hasExtendedAddress() ) - { - to = ar.getWACN() + "-" + ar.getSystemID() + "-" + - ar.getTargetAddress(); - } - - String from = null; - - if( ar.hasAdditionalInformation() && !ar.hasExtendedAddress() ) - { - from = ar.getFromID(); - } + if(raue.isFormat2()) + { + sb.append(" B:"); + sb.append(raue.getWACNB() + "-" + raue.getSystemIDB()); + sb.append(" C:"); + sb.append(raue.getWACNC() + "-" + raue.getSystemIDC()); + sb.append(" D:"); + sb.append(raue.getWACND() + "-" + raue.getSystemIDD()); + } - broadcast( new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( getAliasList() ) - .details( "ACKNOWLEDGE" ) - .from( from ) - .to( to ) - .build() ); - - mLastResponseEventID = ar.getTargetAddress(); - } - break; - case DENY_RESPONSE: - DenyResponse dr = (DenyResponse)message; - - if( mLastResponseEventID == null || !dr.getTargetAddress() - .contentEquals( mLastResponseEventID ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( getAliasList() ) - .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; - - if( mLastResponseEventID == null || !gar.getTargetAddress() - .contentEquals( mLastResponseEventID ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( getAliasList() ) - .details( "AFFILIATION:" + gar.getResponse().name() + - " FOR " + gar.getAffiliationScope() + - " GROUP:" + gar.getGroupAddress() + - " ANNOUNCEMENT GROUP:" + - gar.getAnnouncementGroupAddress() ) - .to( gar.getTargetAddress() ) - .build() ); + if(raue.isFormat3()) + { + sb.append(" E:"); + sb.append(raue.getWACNE() + "-" + raue.getSystemIDE()); + sb.append(" F:"); + sb.append(raue.getWACNF() + "-" + raue.getSystemIDF()); + sb.append(" G:"); + sb.append(raue.getWACNG() + "-" + raue.getSystemIDG()); + } - mLastResponseEventID = gar.getTargetAddress(); - } - break; - case LOCATION_REGISTRATION_RESPONSE: - LocationRegistrationResponse lrr = - (LocationRegistrationResponse)message; + broadcast(new P25CallEvent.Builder(CallEventType.RESPONSE) + .aliasList(getAliasList()) + .details(sb.toString()) + .from(raue.getSourceID()) + .to(raue.getTargetAddress()) + .build()); + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case STATUS_QUERY: + if(pdu instanceof StatusQueryExtended) + { + StatusQueryExtended sq = (StatusQueryExtended) pdu; - if( lrr.getResponse() == Response.ACCEPT ) - { - mRegistrations.put( lrr.getTargetAddress(), - System.currentTimeMillis() ); - } + if(mLastQueryEventID == null || !sq.getTargetAddress() + .contentEquals(mLastQueryEventID)) + { + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .details("STATUS QUERY") + .from(sq.getSourceWACN() + "-" + + sq.getSourceSystemID() + "-" + + sq.getSourceID()) + .to(sq.getTargetAddress()) + .build()); - if( mLastRegistrationEventID == null || - !mLastRegistrationEventID.contentEquals( lrr.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.REGISTER ) - .aliasList( getAliasList() ) - .details( "REGISTRATION:" + lrr.getResponse().name() + - " SITE: " + lrr.getRFSSID() + "-" + - lrr.getSiteID() ) - .from( lrr.getTargetAddress() ) - .to( lrr.getGroupAddress() ) - .build() ); - - mLastRegistrationEventID = lrr.getTargetAddress(); - } - break; - case PROTECTION_PARAMETER_UPDATE: - ProtectionParameterUpdate ppu = (ProtectionParameterUpdate)message; - - if( mLastResponseEventID == null || !ppu.getTargetAddress() - .contentEquals( mLastResponseEventID ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( getAliasList() ) - .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; - - if( mLastResponseEventID == null || !qr.getTargetAddress() - .contentEquals( mLastResponseEventID ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( getAliasList() ) - .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; - - if( mLastResponseEventID == null || !su.getTargetAddress() - .contentEquals( mLastResponseEventID ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.RESPONSE ) - .aliasList( getAliasList() ) - .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; + mLastQueryEventID = sq.getToID(); + } + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case STATUS_UPDATE: + if(pdu instanceof StatusUpdateExtended) + { + StatusUpdateExtended su = (StatusUpdateExtended) pdu; - if( urr.getResponse() == Response.ACCEPT ) - { - mRegistrations.put( urr.getSourceAddress(), - System.currentTimeMillis() ); - } + if(mLastResponseEventID == null || !mLastResponseEventID + .contentEquals(su.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.RESPONSE) + .aliasList(getAliasList()) + .details("STATUS USER: " + su.getUserStatus() + + " UNIT: " + su.getUnitStatus()) + .from(su.getSourceWACN() + "-" + + su.getSourceSystemID() + "-" + + su.getSourceID()) + .to(su.getTargetAddress()) + .build()); - if( mLastRegistrationEventID == null || - !mLastRegistrationEventID.contentEquals( urr.getSourceAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.REGISTER ) - .aliasList( getAliasList() ) - .details( "REGISTRATION:" + urr.getResponse().name() + - " SYSTEM: " + urr.getSystemID() + - " SOURCE ID: " + urr.getSourceID() ) - .from( urr.getSourceAddress() ) - .build() ); - - mLastRegistrationEventID = urr.getSourceAddress(); - } - break; - case UNIT_DEREGISTRATION_ACKNOWLEDGE: - UnitDeregistrationAcknowledge udr = - (UnitDeregistrationAcknowledge)message; + mLastResponseEventID = su.getTargetAddress(); + } + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case UNIT_REGISTRATION_RESPONSE: + if(pdu instanceof UnitRegistrationResponseExtended) + { + UnitRegistrationResponseExtended urr = + (UnitRegistrationResponseExtended) pdu; - if( mLastRegistrationEventID == null || - !mLastRegistrationEventID.contentEquals( udr.getSourceID() ) ) - { - - broadcast( new P25CallEvent.Builder( CallEventType.DEREGISTER ) - .aliasList( getAliasList() ) - .from( udr.getSourceID() ) - .build() ); + if(urr.getResponse() == Response.ACCEPT) + { + mRegistrations.put(urr.getAssignedSourceAddress(), + System.currentTimeMillis()); + } - List keysToRemove = new ArrayList(); + if(mLastRegistrationEventID == null || !mLastRegistrationEventID + .contentEquals(urr.getAssignedSourceAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.REGISTER) + .aliasList(getAliasList()) + .details("REGISTRATION:" + urr.getResponse().name() + + " FOR EXTERNAL SYSTEM ADDRESS: " + + urr.getWACN() + "-" + + urr.getSystemID() + "-" + + urr.getSourceAddress() + + " SOURCE ID: " + urr.getSourceID()) + .from(urr.getAssignedSourceAddress()) + .build()); + + mLastRegistrationEventID = urr.getAssignedSourceAddress(); + } + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case UNIT_TO_UNIT_ANSWER_REQUEST: + if(pdu instanceof UnitToUnitAnswerRequestExplicit) + { + UnitToUnitAnswerRequestExplicit utuare = + (UnitToUnitAnswerRequestExplicit) pdu; + + if(mLastPageEventID == null || !mLastPageEventID + .contentEquals(utuare.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .details((utuare.isEmergency() ? "EMERGENCY" : "")) + .from(utuare.getWACN() + "-" + + utuare.getSystemID() + "-" + + utuare.getSourceID()) + .to(utuare.getTargetAddress()) + .build()); + + mLastPageEventID = utuare.getTargetAddress(); + } + } + else + { + logAlternateVendorMessage(pdu); + } + break; + default: + break; + } + } + } + + private void processPDUChannelGrant(PDUMessage pdu) + { + String channel = null; + String from = null; + String to = null; + + switch(pdu.getOpcode()) + { + case GROUP_DATA_CHANNEL_GRANT: + if(pdu instanceof GroupDataChannelGrantExtended) + { + GroupDataChannelGrantExtended gdcge = + (GroupDataChannelGrantExtended) pdu; + + channel = gdcge.getTransmitChannel(); + from = gdcge.getSourceAddress(); + to = gdcge.getGroupAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent callEvent = new P25CallEvent.Builder(CallEventType.DATA_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((gdcge.isEncrypted() ? "ENCRYPTED" : "") + + (gdcge.isEmergency() ? " EMERGENCY" : "")) + .frequency(gdcge.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(callEvent); + broadcast(callEvent); + } + } + else + { + logAlternateVendorMessage(pdu); + } + + if(!mIgnoreDataCalls) + { + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + } + break; + case GROUP_VOICE_CHANNEL_GRANT: + if(pdu instanceof GroupVoiceChannelGrantExplicit) + { + GroupVoiceChannelGrantExplicit gvcge = + (GroupVoiceChannelGrantExplicit) pdu; + + channel = gvcge.getTransmitChannel(); + from = gvcge.getSourceAddress(); + to = gvcge.getGroupAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent callEvent = new P25CallEvent.Builder(CallEventType.GROUP_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((gvcge.isEncrypted() ? "ENCRYPTED" : "") + + (gvcge.isEmergency() ? " EMERGENCY" : "")) + .frequency(gvcge.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(callEvent); + broadcast(callEvent); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case INDIVIDUAL_DATA_CHANNEL_GRANT: + if(pdu instanceof IndividualDataChannelGrantExtended) + { + IndividualDataChannelGrantExtended idcge = + (IndividualDataChannelGrantExtended) pdu; + + channel = idcge.getTransmitChannel(); + from = idcge.getSourceWACN() + "-" + + idcge.getSourceSystemID() + "-" + + idcge.getSourceAddress(); + to = idcge.getTargetAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent callEvent = new P25CallEvent.Builder(CallEventType.DATA_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((idcge.isEncrypted() ? "ENCRYPTED" : "") + + (idcge.isEmergency() ? " EMERGENCY" : "")) + .frequency(idcge.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(callEvent); + broadcast(callEvent); + } + + if(!mIgnoreDataCalls) + { + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + } + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + TelephoneInterconnectChannelGrantExplicit ticge = + (TelephoneInterconnectChannelGrantExplicit) pdu; + + channel = ticge.getTransmitChannel(); + + //We don't know if the subscriber is calling or being called, so + //we use the same address in both from/to fields + from = ticge.getAddress(); + to = ticge.getAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent callEvent = new P25CallEvent.Builder(CallEventType.TELEPHONE_INTERCONNECT) + .aliasList(getAliasList()) + .channel(channel) + .details((ticge.isEncrypted() ? "ENCRYPTED" : "") + + (ticge.isEmergency() ? " EMERGENCY" : "") + + " CALL TIMER:" + ticge.getCallTimer()) + .frequency(ticge.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(callEvent); + broadcast(callEvent); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + break; + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + if(pdu instanceof UnitToUnitVoiceChannelGrantExtended) + { + UnitToUnitVoiceChannelGrantExtended uuvcge = + (UnitToUnitVoiceChannelGrantExtended) pdu; + + channel = uuvcge.getTransmitChannel(); + from = uuvcge.getSourceWACN() + "-" + + uuvcge.getSourceSystemID() + "-" + + uuvcge.getSourceID(); + to = uuvcge.getTargetAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent callEvent = new P25CallEvent.Builder(CallEventType.UNIT_TO_UNIT_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((uuvcge.isEncrypted() ? "ENCRYPTED" : "") + + (uuvcge.isEmergency() ? " EMERGENCY" : "")) + .frequency(uuvcge.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(callEvent); + broadcast(callEvent); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + } + else + { + logAlternateVendorMessage(pdu); + } + break; + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + if(pdu instanceof UnitToUnitVoiceChannelGrantUpdateExtended) + { + UnitToUnitVoiceChannelGrantUpdateExtended uuvcgue = + (UnitToUnitVoiceChannelGrantUpdateExtended) pdu; + + channel = uuvcgue.getTransmitChannel(); + from = uuvcgue.getSourceWACN() + "-" + + uuvcgue.getSourceSystemID() + "-" + + uuvcgue.getSourceID(); + to = uuvcgue.getTargetAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent callEvent = new P25CallEvent.Builder(CallEventType.UNIT_TO_UNIT_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((uuvcgue.isEncrypted() ? "ENCRYPTED" : "") + + (uuvcgue.isEmergency() ? " EMERGENCY" : "")) + .frequency(uuvcgue.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(callEvent); + broadcast(callEvent); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + + } + else + { + logAlternateVendorMessage(pdu); + } + break; + default: + break; + } + } + + private void processTSBKCommand(TSBKMessage message) + { + switch(message.getOpcode()) + { + case AUTHENTICATION_COMMAND: + AuthenticationCommand ac = (AuthenticationCommand) message; + + if(mLastCommandEventID == null || !mLastCommandEventID + .contentEquals(ac.getFullTargetID())) + { + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .details("AUTHENTICATE") + .to(ac.getWACN() + "-" + ac.getSystemID() + "-" + + ac.getTargetID()) + .build()); + + mLastCommandEventID = ac.getFullTargetID(); + } + break; + case EXTENDED_FUNCTION_COMMAND: + ExtendedFunctionCommand efc = (ExtendedFunctionCommand) message; + + if(mLastCommandEventID == null || !mLastCommandEventID + .contentEquals(efc.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.FUNCTION) + .aliasList(getAliasList()) + .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; + + if(mLastCommandEventID == null || !mLastCommandEventID + .contentEquals(rumc.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .details("RADIO UNIT MONITOR") + .from(rumc.getSourceAddress()) + .to(rumc.getTargetAddress()) + .build()); + + mLastCommandEventID = rumc.getTargetAddress(); + } + break; + case ROAMING_ADDRESS_COMMAND: + RoamingAddressCommand rac = (RoamingAddressCommand) message; + + if(mLastCommandEventID == null || !mLastCommandEventID + .contentEquals(rac.getTargetID())) + { + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .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; + + if(mLastCommandEventID == null || !mLastCommandEventID + .contentEquals(urc.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.COMMAND) + .aliasList(getAliasList()) + .details("REGISTER") + .from(urc.getSourceAddress()) + .to(urc.getTargetAddress()) + .build()); + + mLastCommandEventID = urc.getTargetAddress(); + } + break; + default: + break; + } + } + + private void processTSBKMessage(TSBKMessage message) + { + MessageUpdate mu = (MessageUpdate) message; + + broadcast(new P25CallEvent.Builder(CallEventType.SDM) + .aliasList(getAliasList()) + .details("MESSAGE: " + mu.getMessage()) + .from(mu.getSourceAddress()) + .to(mu.getTargetAddress()) + .build()); + } + + private void processTSBKQuery(TSBKMessage message) + { + switch(message.getOpcode()) + { + case GROUP_AFFILIATION_QUERY: + GroupAffiliationQuery gaq = (GroupAffiliationQuery) message; + + if(mLastQueryEventID == null || !mLastQueryEventID + .contentEquals(gaq.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .details("GROUP AFFILIATION") + .from(gaq.getSourceAddress()) + .to(gaq.getTargetAddress()) + .build()); + } + break; + case STATUS_QUERY: + StatusQuery sq = (StatusQuery) message; + + if(mLastQueryEventID == null || !mLastQueryEventID + .contentEquals(sq.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.QUERY) + .aliasList(getAliasList()) + .details("STATUS QUERY") + .from(sq.getSourceAddress()) + .to(sq.getTargetAddress()) + .build()); + } + break; + default: + break; + } + } + + private void processTSBKResponse(TSBKMessage message) + { + switch(message.getOpcode()) + { + case ACKNOWLEDGE_RESPONSE: + AcknowledgeResponse ar = (AcknowledgeResponse) message; + + if(mLastResponseEventID == null || !ar.getTargetAddress() + .contentEquals(mLastResponseEventID)) + { + String to = ar.getTargetAddress(); + + if(ar.hasAdditionalInformation() && ar.hasExtendedAddress()) + { + to = ar.getWACN() + "-" + ar.getSystemID() + "-" + + ar.getTargetAddress(); + } + + String from = null; + + if(ar.hasAdditionalInformation() && !ar.hasExtendedAddress()) + { + from = ar.getFromID(); + } + + broadcast(new P25CallEvent.Builder(CallEventType.RESPONSE) + .aliasList(getAliasList()) + .details("ACKNOWLEDGE") + .from(from) + .to(to) + .build()); + + mLastResponseEventID = ar.getTargetAddress(); + } + break; + case DENY_RESPONSE: + DenyResponse dr = (DenyResponse) message; + + if(mLastResponseEventID == null || !dr.getTargetAddress() + .contentEquals(mLastResponseEventID)) + { + broadcast(new P25CallEvent.Builder(CallEventType.RESPONSE) + .aliasList(getAliasList()) + .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; + + if(mLastResponseEventID == null || !gar.getTargetAddress() + .contentEquals(mLastResponseEventID)) + { + broadcast(new P25CallEvent.Builder(CallEventType.RESPONSE) + .aliasList(getAliasList()) + .details("AFFILIATION:" + gar.getResponse().name() + + " FOR " + gar.getAffiliationScope() + + " GROUP:" + gar.getGroupAddress() + + " ANNOUNCEMENT GROUP:" + + gar.getAnnouncementGroupAddress()) + .to(gar.getTargetAddress()) + .build()); + + mLastResponseEventID = gar.getTargetAddress(); + } + break; + case LOCATION_REGISTRATION_RESPONSE: + LocationRegistrationResponse lrr = + (LocationRegistrationResponse) message; + + if(lrr.getResponse() == Response.ACCEPT) + { + mRegistrations.put(lrr.getTargetAddress(), + System.currentTimeMillis()); + } + + if(mLastRegistrationEventID == null || + !mLastRegistrationEventID.contentEquals(lrr.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.REGISTER) + .aliasList(getAliasList()) + .details("REGISTRATION:" + lrr.getResponse().name() + + " SITE: " + lrr.getRFSSID() + "-" + + lrr.getSiteID()) + .from(lrr.getTargetAddress()) + .to(lrr.getGroupAddress()) + .build()); + + mLastRegistrationEventID = lrr.getTargetAddress(); + } + break; + case PROTECTION_PARAMETER_UPDATE: + ProtectionParameterUpdate ppu = (ProtectionParameterUpdate) message; + + if(mLastResponseEventID == null || !ppu.getTargetAddress() + .contentEquals(mLastResponseEventID)) + { + broadcast(new P25CallEvent.Builder(CallEventType.RESPONSE) + .aliasList(getAliasList()) + .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; + + if(mLastResponseEventID == null || !qr.getTargetAddress() + .contentEquals(mLastResponseEventID)) + { + broadcast(new P25CallEvent.Builder(CallEventType.RESPONSE) + .aliasList(getAliasList()) + .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; + + if(mLastResponseEventID == null || !su.getTargetAddress() + .contentEquals(mLastResponseEventID)) + { + broadcast(new P25CallEvent.Builder(CallEventType.RESPONSE) + .aliasList(getAliasList()) + .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; + + if(urr.getResponse() == Response.ACCEPT) + { + mRegistrations.put(urr.getSourceAddress(), + System.currentTimeMillis()); + } + + if(mLastRegistrationEventID == null || + !mLastRegistrationEventID.contentEquals(urr.getSourceAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.REGISTER) + .aliasList(getAliasList()) + .details("REGISTRATION:" + urr.getResponse().name() + + " SYSTEM: " + urr.getSystemID() + + " SOURCE ID: " + urr.getSourceID()) + .from(urr.getSourceAddress()) + .build()); + + mLastRegistrationEventID = urr.getSourceAddress(); + } + break; + case UNIT_DEREGISTRATION_ACKNOWLEDGE: + UnitDeregistrationAcknowledge udr = + (UnitDeregistrationAcknowledge) message; + + if(mLastRegistrationEventID == null || + !mLastRegistrationEventID.contentEquals(udr.getSourceID())) + { + + broadcast(new P25CallEvent.Builder(CallEventType.DEREGISTER) + .aliasList(getAliasList()) + .from(udr.getSourceID()) + .build()); + + List keysToRemove = new ArrayList(); /* Remove this radio from the registrations set */ - for( String key: mRegistrations.keySet() ) - { - if( key.startsWith( udr.getSourceID() ) ) - { - keysToRemove.add( key ); - } - } - - for( String key: keysToRemove ) - { - mRegistrations.remove( key ); - } + for(String key : mRegistrations.keySet()) + { + if(key.startsWith(udr.getSourceID())) + { + keysToRemove.add(key); + } + } - mLastRegistrationEventID = udr.getSourceID(); - } - break; - default: - break; - } - } - - private void processTSBKRFSSStatus( RFSSStatusBroadcast message ) - { - mRFSSStatusMessage = message; - - updateNAC( message.getNAC() ); - - updateSystem( message.getSystemID() ); - - updateSite( message.getRFSubsystemID() + "-" + message.getSiteID() ); - } - - private void processTSBKDataChannelAnnouncement( TSBKMessage message ) - { - switch( message.getOpcode() ) - { - case GROUP_DATA_CHANNEL_ANNOUNCEMENT: - GroupDataChannelAnnouncement gdca = (GroupDataChannelAnnouncement)message; - - broadcast( new P25CallEvent.Builder( CallEventType.ANNOUNCEMENT ) - .aliasList( getAliasList() ) - .channel( gdca.getChannel1() ) - .details( ( gdca.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gdca.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gdca.getDownlinkFrequency1() ) - .to( gdca.getGroupAddress1() ) - .build() ); - - if( gdca.hasChannelNumber2() ) - { - broadcast( new P25CallEvent.Builder( CallEventType.ANNOUNCEMENT ) - .aliasList( getAliasList() ) - .channel( gdca.getChannel2() ) - .details( ( gdca.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gdca.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gdca.getDownlinkFrequency2() ) - .to( gdca.getGroupAddress2() ) - .build() ); - } - break; - case GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: - GroupDataChannelAnnouncementExplicit gdcae = - (GroupDataChannelAnnouncementExplicit)message; - - broadcast( new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( gdcae.getTransmitChannel() ) - .details( ( gdcae.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gdcae.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gdcae.getDownlinkFrequency() ) - .to( gdcae.getGroupAddress() ) - .build() ); - break; - default: - break; - } - } + for(String key : keysToRemove) + { + mRegistrations.remove(key); + } - /** - * Process Motorola vendor-specific Trunking Signaling Block messages + mLastRegistrationEventID = udr.getSourceID(); + } + break; + default: + break; + } + } + + private void processTSBKRFSSStatus(RFSSStatusBroadcast message) + { + mRFSSStatusMessage = message; + + updateNAC(message.getNAC()); + + updateSystem(message.getSystemID()); + + updateSite(message.getRFSubsystemID() + "-" + message.getSiteID()); + } + + private void processTSBKDataChannelAnnouncement(TSBKMessage message) + { + switch(message.getOpcode()) + { + case GROUP_DATA_CHANNEL_ANNOUNCEMENT: + GroupDataChannelAnnouncement gdca = (GroupDataChannelAnnouncement) message; + + broadcast(new P25CallEvent.Builder(CallEventType.ANNOUNCEMENT) + .aliasList(getAliasList()) + .channel(gdca.getChannel1()) + .details((gdca.isEncrypted() ? "ENCRYPTED" : "") + + (gdca.isEmergency() ? " EMERGENCY" : "")) + .frequency(gdca.getDownlinkFrequency1()) + .to(gdca.getGroupAddress1()) + .build()); + + if(gdca.hasChannelNumber2()) + { + broadcast(new P25CallEvent.Builder(CallEventType.ANNOUNCEMENT) + .aliasList(getAliasList()) + .channel(gdca.getChannel2()) + .details((gdca.isEncrypted() ? "ENCRYPTED" : "") + + (gdca.isEmergency() ? " EMERGENCY" : "")) + .frequency(gdca.getDownlinkFrequency2()) + .to(gdca.getGroupAddress2()) + .build()); + } + break; + case GROUP_DATA_CHANNEL_ANNOUNCEMENT_EXPLICIT: + GroupDataChannelAnnouncementExplicit gdcae = + (GroupDataChannelAnnouncementExplicit) message; + + broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL) + .aliasList(getAliasList()) + .channel(gdcae.getTransmitChannel()) + .details((gdcae.isEncrypted() ? "ENCRYPTED" : "") + + (gdcae.isEmergency() ? " EMERGENCY" : "")) + .frequency(gdcae.getDownlinkFrequency()) + .to(gdcae.getGroupAddress()) + .build()); + break; + default: + break; + } + } + + /** + * Process Motorola vendor-specific Trunking Signaling Block messages */ - private void processMotorolaTSBK(MotorolaTSBKMessage tsbk) - { - String channel; - String from; - String to; + private void processMotorolaTSBK(MotorolaTSBKMessage tsbk) + { + String channel; + String from; + String to; - switch(((MotorolaTSBKMessage)tsbk).getMotorolaOpcode()) - { - case PATCH_GROUP_CHANNEL_GRANT: - PatchGroupVoiceChannelGrant gvcg = (PatchGroupVoiceChannelGrant)tsbk; + switch(((MotorolaTSBKMessage) tsbk).getMotorolaOpcode()) + { + case PATCH_GROUP_CHANNEL_GRANT: + PatchGroupVoiceChannelGrant gvcg = (PatchGroupVoiceChannelGrant) tsbk; - channel = gvcg.getChannel(); - from = gvcg.getSourceAddress(); - to = gvcg.getPatchGroupAddress(); + channel = gvcg.getChannel(); + from = gvcg.getSourceAddress(); + to = gvcg.getPatchGroupAddress(); - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( CallEventType.PATCH_GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( gvcg.isEncrypted() ? "ENCRYPTED " : "" ) + - ( gvcg.isEmergency() ? " EMERGENCY " : "") + - "PATCH SESSION MODE:" + gvcg.getSessionMode().name()) - .frequency( gvcg.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((gvcg.isEncrypted() ? "ENCRYPTED " : "") + + (gvcg.isEmergency() ? " EMERGENCY " : "") + + "PATCH SESSION MODE:" + gvcg.getSessionMode().name()) + .frequency(gvcg.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); - registerCallEvent( event ); - broadcast( event ); - } + registerCallEvent(event); + broadcast(event); + } - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - break; - case PATCH_GROUP_CHANNEL_GRANT_UPDATE: - PatchGroupVoiceChannelGrantUpdate gvcgu = (PatchGroupVoiceChannelGrantUpdate) tsbk; + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + break; + case PATCH_GROUP_CHANNEL_GRANT_UPDATE: + PatchGroupVoiceChannelGrantUpdate gvcgu = (PatchGroupVoiceChannelGrantUpdate) tsbk; - channel = gvcgu.getChannel1(); - to = gvcgu.getPatchGroupAddress1(); + channel = gvcgu.getChannel1(); + to = gvcgu.getPatchGroupAddress1(); - if( hasCallEvent( channel, null, to ) ) - { - updateCallEvent( channel, null, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( CallEventType.PATCH_GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( gvcgu.isEncrypted() ? "ENCRYPTED " : "" ) + - "PATCH UPDATE - GROUP 2:" + gvcgu.getPatchGroupAddress2() + - " DN:" + gvcgu.getDownlinkFrequency2()) - .frequency( gvcgu.getDownlinkFrequency1() ) - .to( to ) - .build(); + if(hasCallEvent(channel, null, to)) + { + updateCallEvent(channel, null, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((gvcgu.isEncrypted() ? "ENCRYPTED " : "") + + "PATCH UPDATE - GROUP 2:" + gvcgu.getPatchGroupAddress2() + + " DN:" + gvcgu.getDownlinkFrequency2()) + .frequency(gvcgu.getDownlinkFrequency1()) + .to(to) + .build(); - registerCallEvent( event ); - broadcast( event ); - } + registerCallEvent(event); + broadcast(event); + } - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - break; - case PATCH_GROUP_ADD: - PatchGroupAdd pga = (PatchGroupAdd)tsbk; + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + break; + case PATCH_GROUP_ADD: + PatchGroupAdd pga = (PatchGroupAdd) tsbk; - StringBuilder sb = new StringBuilder(); + if(addPatchGroup(pga.getPatchGroupAddress(), pga.getPatchedTalkgroups())) + { + StringBuilder sb = new StringBuilder(); - sb.append("PATCH GROUP:").append(pga.getPatchGroupAddress()); - sb.append(" 1:").append(pga.getGroupAddress1()); - sb.append(" 2:").append(pga.getGroupAddress2()); - sb.append(" 3:").append(pga.getGroupAddress3()); + sb.append("PATCH GROUP:").append(pga.getPatchGroupAddress()); + sb.append(" ").append(pga.getPatchedTalkgroups()); - broadcast( new P25CallEvent.Builder( CallEventType.PATCH_GROUP_ADD ) - .aliasList( getAliasList() ) - .details( sb.toString() ) - .build() ); - break; - case PATCH_GROUP_DELETE: - PatchGroupDelete pgd = (PatchGroupDelete)tsbk; + broadcast(new P25CallEvent.Builder(CallEventType.PATCH_GROUP_ADD) + .aliasList(getAliasList()) + .details(sb.toString()) + .build()); + } + break; + case PATCH_GROUP_DELETE: + PatchGroupDelete pgd = (PatchGroupDelete) tsbk; - StringBuilder sbpgd = new StringBuilder(); + if(removePatchGroup(pgd.getPatchGroupAddress(), pgd.getPatchedTalkgroups())) + { + StringBuilder sbpgd = new StringBuilder(); - sbpgd.append("PATCH GROUP:").append(pgd.getPatchGroupAddress()); - sbpgd.append(" 1:").append(pgd.getGroupAddress1()); - sbpgd.append(" 2:").append(pgd.getGroupAddress2()); - sbpgd.append(" 3:").append(pgd.getGroupAddress3()); + sbpgd.append("PATCH GROUP:").append(pgd.getPatchGroupAddress()); + sbpgd.append(" ").append(pgd.getPatchedTalkgroups()); - broadcast( new P25CallEvent.Builder( CallEventType.PATCH_GROUP_DELETE ) - .aliasList( getAliasList() ) - .details( sbpgd.toString() ) - .build() ); - break; - case CCH_PLANNED_SHUTDOWN: - if( !mControlChannelShutdownLogged ) - { - broadcast( new P25CallEvent.Builder( CallEventType.NOTIFICATION ) - .details( "PLANNED CONTROL CHANNEL SHUTDOWN" ) - .build() ); + broadcast(new P25CallEvent.Builder(CallEventType.PATCH_GROUP_DELETE) + .aliasList(getAliasList()) + .details(sbpgd.toString()) + .build()); + } + break; + case CCH_PLANNED_SHUTDOWN: + if(!mControlChannelShutdownLogged) + { + broadcast(new P25CallEvent.Builder(CallEventType.NOTIFICATION) + .details("PLANNED CONTROL CHANNEL SHUTDOWN") + .build()); - mControlChannelShutdownLogged = true; - } - break; - } - } + mControlChannelShutdownLogged = true; + } + break; + } + } - /** - * Process a traffic channel allocation message - */ - private void processTSBKChannelGrant( TSBKMessage message ) - { - String channel = null; - String from = null; - String to = null; - - switch( message.getOpcode() ) - { - case GROUP_DATA_CHANNEL_GRANT: - GroupDataChannelGrant gdcg = (GroupDataChannelGrant)message; + /** + * Creates a new patch group or updates an existing patch group with the patched talkgroup entries. + * + * @param patchGroupID for the patch group + * @param talkgroupsToAdd to include in the patch group + * @return true if the patch group or the patch group map was updated + */ + private boolean addPatchGroup(String patchGroupID, List talkgroupsToAdd) + { + boolean updated = false; - channel = gdcg.getChannel(); - from = gdcg.getSourceAddress(); - to = gdcg.getGroupAddress(); + if(patchGroupID != null && !talkgroupsToAdd.isEmpty()) + { + List patchedTalkgroups = mPatchGroupMap.get(patchGroupID); - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( gdcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gdcg.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gdcg.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); + if(patchedTalkgroups == null) + { + patchedTalkgroups = new ArrayList<>(); + } - registerCallEvent( event ); - broadcast( event ); - } - - if( !mIgnoreDataCalls ) - { - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - } - break; - case GROUP_VOICE_CHANNEL_GRANT: - GroupVoiceChannelGrant gvcg = (GroupVoiceChannelGrant)message; + for(String talkgroupToAdd: talkgroupsToAdd) + { + if(!patchedTalkgroups.contains(talkgroupToAdd)) + { + patchedTalkgroups.add(talkgroupToAdd); + updated = true; + } + } - channel = gvcg.getChannel(); - from = gvcg.getSourceAddress(); - to = gvcg.getGroupAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( gvcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcg.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcg.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( event ); - broadcast( event ); - } + if(updated) + { + mPatchGroupMap.put(patchGroupID, patchedTalkgroups); + updatePatchAlias(patchGroupID, patchedTalkgroups); + } + } - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - break; - case GROUP_VOICE_CHANNEL_GRANT_UPDATE: - GroupVoiceChannelGrantUpdate gvcgu = - (GroupVoiceChannelGrantUpdate)message; + return updated; + } - channel = gvcgu.getChannel1(); - from = null; - to = gvcgu.getGroupAddress1(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .channel( gvcgu.getChannel1() ) - .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcgu.getDownlinkFrequency1() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( event ); - broadcast( event ); - } - - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); + /** + * Removes the patched talkgroups from the patch group and removes the patch group from the patch group map if + * there are no more patched talkgroups in the patch group. + * + * @param patchGroupID for the patch group + * @param talkgroupsToRemove to remove from the patch group + * @return true if the patch group or the patch group map was updated + */ + private boolean removePatchGroup(String patchGroupID, List talkgroupsToRemove) + { + boolean updated = false; - if( gvcgu.hasChannelNumber2() ) - { - channel = gvcgu.getChannel2(); - to = gvcgu.getGroupAddress2(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event2 = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( gvcgu.getChannel2() ) - .details( ( gvcgu.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcgu.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcgu.getDownlinkFrequency2() ) - .to( gvcgu.getGroupAddress2() ) - .build(); - - registerCallEvent( event2 ); - broadcast( event2 ); - } - - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - } - break; - case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: - GroupVoiceChannelGrantUpdateExplicit gvcgue = - (GroupVoiceChannelGrantUpdateExplicit)message; + if(patchGroupID != null && !talkgroupsToRemove.isEmpty()) + { + List patchedTalkgroups = mPatchGroupMap.get(patchGroupID); - channel = gvcgue.getTransmitChannelIdentifier() + "-" + - gvcgue.getTransmitChannelNumber(); - from = null; - to = gvcgue.getGroupAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( CallEventType.GROUP_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( gvcgue.isEncrypted() ? "ENCRYPTED" : "" ) + - ( gvcgue.isEmergency() ? " EMERGENCY" : "") ) - .frequency( gvcgue.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( event ); - broadcast( event ); - } + if(patchedTalkgroups != null) + { + for(String talkgroupToRemove: talkgroupsToRemove) + { + if(patchedTalkgroups.contains(talkgroupToRemove)) + { + patchedTalkgroups.remove(talkgroupToRemove); + updated = true; + } + } - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - break; - case INDIVIDUAL_DATA_CHANNEL_GRANT: - IndividualDataChannelGrant idcg = (IndividualDataChannelGrant)message; + if(patchedTalkgroups.isEmpty()) + { + mPatchGroupMap.remove(patchGroupID); + removePatchGroupAlias(patchGroupID); + updated = true; + } + else + { + updatePatchAlias(patchGroupID, patchedTalkgroups); + } + } + } - channel = idcg.getChannel(); - from = idcg.getSourceAddress(); - to = idcg.getTargetAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( idcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( idcg.isEmergency() ? " EMERGENCY" : "") ) - .frequency( idcg.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( event ); - broadcast( event ); - } - - if( !mIgnoreDataCalls ) - { - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - } - break; - case SNDCP_DATA_CHANNEL_GRANT: - SNDCPDataChannelGrant sdcg = (SNDCPDataChannelGrant)message; - - channel = sdcg.getTransmitChannel(); - from = null; - to = sdcg.getTargetAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( CallEventType.DATA_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( sdcg.isEncrypted() ? "ENCRYPTED" : "" ) + - ( sdcg.isEmergency() ? " EMERGENCY" : "") ) - .frequency( sdcg.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( event ); - broadcast( event ); - } - - if( !mIgnoreDataCalls ) - { - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - } - break; - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: - TelephoneInterconnectVoiceChannelGrant tivcg = - (TelephoneInterconnectVoiceChannelGrant)message; - - channel = tivcg.getChannel(); - from = null; + return updated; + } + + /** + * Adds/updates a patch group alias to the alias list containing aliases for each of the patched talkgroups + * + * @param patchGroupID for the patch group + * @param patchedTalkgroups containing the talkgroup IDs for each of the patched talkgroups + */ + private void updatePatchAlias(String patchGroupID, List patchedTalkgroups) + { + PatchGroupAlias patchGroupAlias = null; + + //Check for an existing alias for the patch talkgroup + Alias alias = getAliasList().getTalkgroupAlias(patchGroupID); + + if(alias instanceof PatchGroupAlias) + { + patchGroupAlias = (PatchGroupAlias)alias; + } + else if(alias != null) + { + //If the talkgroup is already aliased to a non-patch group, do nothing + } + else + { + patchGroupAlias = new PatchGroupAlias(); + patchGroupAlias.addAliasID(new TalkgroupID(patchGroupID)); + getAliasList().addAlias(patchGroupAlias); + } + + if(patchGroupAlias != null) + { + patchGroupAlias.clearPatchedAliases(); + + for(String patchedTalkgroup: patchedTalkgroups) + { + Alias patchedAlias = getAliasList().getTalkgroupAlias(patchedTalkgroup); + + if(patchedAlias != null) + { + patchGroupAlias.addPatchedAlias(patchedAlias); + } + } + } + } + + /** + * Removes the temporary patch group alias from the alias list + */ + private void removePatchGroupAlias(String patchGroupID) + { + Alias alias = getAliasList().getTalkgroupAlias(patchGroupID); + + if(alias instanceof PatchGroupAlias) + { + getAliasList().removeAlias(alias); + } + } + + /** + * Process a traffic channel allocation message + */ + private void processTSBKChannelGrant(TSBKMessage message) + { + String channel = null; + String from = null; + String to = null; + + switch(message.getOpcode()) + { + case GROUP_DATA_CHANNEL_GRANT: + GroupDataChannelGrant gdcg = (GroupDataChannelGrant) message; + + channel = gdcg.getChannel(); + from = gdcg.getSourceAddress(); + to = gdcg.getGroupAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((gdcg.isEncrypted() ? "ENCRYPTED" : "") + + (gdcg.isEmergency() ? " EMERGENCY" : "")) + .frequency(gdcg.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(event); + broadcast(event); + } + + if(!mIgnoreDataCalls) + { + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + } + break; + case GROUP_VOICE_CHANNEL_GRANT: + GroupVoiceChannelGrant gvcg = (GroupVoiceChannelGrant) message; + + channel = gvcg.getChannel(); + from = gvcg.getSourceAddress(); + to = gvcg.getGroupAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((gvcg.isEncrypted() ? "ENCRYPTED" : "") + + (gvcg.isEmergency() ? " EMERGENCY" : "")) + .frequency(gvcg.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(event); + broadcast(event); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + break; + case GROUP_VOICE_CHANNEL_GRANT_UPDATE: + GroupVoiceChannelGrantUpdate gvcgu = + (GroupVoiceChannelGrantUpdate) message; + + channel = gvcgu.getChannel1(); + from = null; + to = gvcgu.getGroupAddress1(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL) + .aliasList(getAliasList()) + .channel(channel) + .channel(gvcgu.getChannel1()) + .details((gvcgu.isEncrypted() ? "ENCRYPTED" : "") + + (gvcgu.isEmergency() ? " EMERGENCY" : "")) + .frequency(gvcgu.getDownlinkFrequency1()) + .from(from) + .to(to) + .build(); + + registerCallEvent(event); + broadcast(event); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + + if(gvcgu.hasChannelNumber2()) + { + channel = gvcgu.getChannel2(); + to = gvcgu.getGroupAddress2(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event2 = new P25CallEvent.Builder(CallEventType.GROUP_CALL) + .aliasList(getAliasList()) + .channel(gvcgu.getChannel2()) + .details((gvcgu.isEncrypted() ? "ENCRYPTED" : "") + + (gvcgu.isEmergency() ? " EMERGENCY" : "")) + .frequency(gvcgu.getDownlinkFrequency2()) + .to(gvcgu.getGroupAddress2()) + .build(); + + registerCallEvent(event2); + broadcast(event2); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + } + break; + case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT: + GroupVoiceChannelGrantUpdateExplicit gvcgue = + (GroupVoiceChannelGrantUpdateExplicit) message; + + channel = gvcgue.getTransmitChannelIdentifier() + "-" + + gvcgue.getTransmitChannelNumber(); + from = null; + to = gvcgue.getGroupAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((gvcgue.isEncrypted() ? "ENCRYPTED" : "") + + (gvcgue.isEmergency() ? " EMERGENCY" : "")) + .frequency(gvcgue.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(event); + broadcast(event); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + break; + case INDIVIDUAL_DATA_CHANNEL_GRANT: + IndividualDataChannelGrant idcg = (IndividualDataChannelGrant) message; + + channel = idcg.getChannel(); + from = idcg.getSourceAddress(); + to = idcg.getTargetAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((idcg.isEncrypted() ? "ENCRYPTED" : "") + + (idcg.isEmergency() ? " EMERGENCY" : "")) + .frequency(idcg.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(event); + broadcast(event); + } + + if(!mIgnoreDataCalls) + { + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + } + break; + case SNDCP_DATA_CHANNEL_GRANT: + SNDCPDataChannelGrant sdcg = (SNDCPDataChannelGrant) message; + + channel = sdcg.getTransmitChannel(); + from = null; + to = sdcg.getTargetAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((sdcg.isEncrypted() ? "ENCRYPTED" : "") + + (sdcg.isEmergency() ? " EMERGENCY" : "")) + .frequency(sdcg.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(event); + broadcast(event); + } + + if(!mIgnoreDataCalls) + { + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + } + break; + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT: + TelephoneInterconnectVoiceChannelGrant tivcg = + (TelephoneInterconnectVoiceChannelGrant) message; + + channel = tivcg.getChannel(); + from = null; /* Address is ambiguous and could mean either source or target, * so we'll place the value in the to field */ - to = tivcg.getAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( - CallEventType.TELEPHONE_INTERCONNECT ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( tivcg.isEncrypted() ? "ENCRYPTED " : "" ) + - ( tivcg.isEmergency() ? "EMERGENCY " : "") + - "CALL TIMER:" + tivcg.getCallTimer() ) - .frequency( tivcg.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( event ); - broadcast( event ); - } + to = tivcg.getAddress(); - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - break; - case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: - TelephoneInterconnectVoiceChannelGrantUpdate tivcgu = - (TelephoneInterconnectVoiceChannelGrantUpdate)message; + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( + CallEventType.TELEPHONE_INTERCONNECT) + .aliasList(getAliasList()) + .channel(channel) + .details((tivcg.isEncrypted() ? "ENCRYPTED " : "") + + (tivcg.isEmergency() ? "EMERGENCY " : "") + + "CALL TIMER:" + tivcg.getCallTimer()) + .frequency(tivcg.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(event); + broadcast(event); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + break; + case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE: + TelephoneInterconnectVoiceChannelGrantUpdate tivcgu = + (TelephoneInterconnectVoiceChannelGrantUpdate) message; + + channel = tivcgu.getChannelIdentifier() + "-" + + tivcgu.getChannelNumber(); + from = null; - channel = tivcgu.getChannelIdentifier() + "-" + - tivcgu.getChannelNumber(); - from = null; - /* Address is ambiguous and could mean either source or target, * so we'll place the value in the to field */ - to = tivcgu.getAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( - CallEventType.TELEPHONE_INTERCONNECT ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( tivcgu.isEncrypted() ? "ENCRYPTED " : "" ) + - ( tivcgu.isEmergency() ? "EMERGENCY " : "") + - "CALL TIMER:" + tivcgu.getCallTimer() ) - .frequency( tivcgu.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( event ); - broadcast( event ); - } + to = tivcgu.getAddress(); - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - break; - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: - UnitToUnitVoiceChannelGrant uuvcg = - (UnitToUnitVoiceChannelGrant)message; + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( + CallEventType.TELEPHONE_INTERCONNECT) + .aliasList(getAliasList()) + .channel(channel) + .details((tivcgu.isEncrypted() ? "ENCRYPTED " : "") + + (tivcgu.isEmergency() ? "EMERGENCY " : "") + + "CALL TIMER:" + tivcgu.getCallTimer()) + .frequency(tivcgu.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); - channel = uuvcg.getChannelIdentifier() + "-" + - uuvcg.getChannelNumber(); - from = uuvcg.getSourceAddress(); - to = uuvcg.getTargetAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( - CallEventType.UNIT_TO_UNIT_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( uuvcg.isEncrypted() ? "ENCRYPTED " : "" ) + - ( uuvcg.isEmergency() ? "EMERGENCY " : "") ) - .frequency( uuvcg.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( event ); - broadcast( event ); - } - - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - break; - case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: - UnitToUnitVoiceChannelGrantUpdate uuvcgu = - (UnitToUnitVoiceChannelGrantUpdate)message; + registerCallEvent(event); + broadcast(event); + } - channel = uuvcgu.getChannelIdentifier() + "-" + - uuvcgu.getChannelNumber(); - from = uuvcgu.getSourceAddress(); - to = uuvcgu.getTargetAddress(); - - if( hasCallEvent( channel, from, to ) ) - { - updateCallEvent( channel, from, to ); - } - else - { - P25CallEvent event = new P25CallEvent.Builder( - CallEventType.UNIT_TO_UNIT_CALL ) - .aliasList( getAliasList() ) - .channel( channel ) - .details( ( uuvcgu.isEncrypted() ? "ENCRYPTED " : "" ) + - ( uuvcgu.isEmergency() ? "EMERGENCY " : "") ) - .frequency( uuvcgu.getDownlinkFrequency() ) - .from( from ) - .to( to ) - .build(); - - registerCallEvent( event ); - broadcast( event ); - } - - broadcast( new TrafficChannelAllocationEvent( this, - mChannelCallMap.get( channel ) ) ); - break; - default: - break; - } - } - - /** - * Broadcasts an update for the TO group or unit address - */ - private void updateTo( final String to ) - { - if( to != null && !to.contentEquals( "0000" ) && !to.contentEquals( "000000" ) ) - { - if( mToTalkgroup == null || !mToTalkgroup.contentEquals( to ) ) - { - mToTalkgroup = to; - - broadcast( ChangedAttribute.TO_TALKGROUP ); + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + break; + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT: + UnitToUnitVoiceChannelGrant uuvcg = + (UnitToUnitVoiceChannelGrant) message; - if( hasAliasList() ) - { - mToAlias = getAliasList().getTalkgroupAlias( mToTalkgroup ); - } - else - { - mToAlias = null; - } + channel = uuvcg.getChannelIdentifier() + "-" + + uuvcg.getChannelNumber(); + from = uuvcg.getSourceAddress(); + to = uuvcg.getTargetAddress(); - broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( + CallEventType.UNIT_TO_UNIT_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((uuvcg.isEncrypted() ? "ENCRYPTED " : "") + + (uuvcg.isEmergency() ? "EMERGENCY " : "")) + .frequency(uuvcg.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(event); + broadcast(event); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + break; + case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE: + UnitToUnitVoiceChannelGrantUpdate uuvcgu = + (UnitToUnitVoiceChannelGrantUpdate) message; + + channel = uuvcgu.getChannelIdentifier() + "-" + + uuvcgu.getChannelNumber(); + from = uuvcgu.getSourceAddress(); + to = uuvcgu.getTargetAddress(); + + if(hasCallEvent(channel, from, to)) + { + updateCallEvent(channel, from, to); + } + else + { + P25CallEvent event = new P25CallEvent.Builder( + CallEventType.UNIT_TO_UNIT_CALL) + .aliasList(getAliasList()) + .channel(channel) + .details((uuvcgu.isEncrypted() ? "ENCRYPTED " : "") + + (uuvcgu.isEmergency() ? "EMERGENCY " : "")) + .frequency(uuvcgu.getDownlinkFrequency()) + .from(from) + .to(to) + .build(); + + registerCallEvent(event); + broadcast(event); + } + + broadcast(new TrafficChannelAllocationEvent(this, + mChannelCallMap.get(channel))); + break; + default: + break; + } + } + + /** + * Broadcasts an update for the TO group or unit address + */ + private void updateTo(final String to) + { + if(to != null && !to.contentEquals("0000") && !to.contentEquals("000000")) + { + if(mToTalkgroup == null || !mToTalkgroup.contentEquals(to)) + { + mToTalkgroup = to; + + broadcast(ChangedAttribute.TO_TALKGROUP); + + if(hasAliasList()) + { + mToAlias = getAliasList().getTalkgroupAlias(mToTalkgroup); + } + else + { + mToAlias = null; + } + + broadcast(ChangedAttribute.TO_TALKGROUP_ALIAS); /* Send audio metadata update */ - broadcast( new Metadata( MetadataType.TO, mToTalkgroup, mToAlias, true ) ); + broadcast(new Metadata(MetadataType.TO, mToTalkgroup, mToAlias, true)); - if( mCurrentCallEvent != null && - ( mCurrentCallEvent.getToID() == null || - !mCurrentCallEvent.getToID().contentEquals( to ) ) ) - { - mCurrentCallEvent.setToID( to ); - broadcast( mCurrentCallEvent ); - } - } - } - } + if(mCurrentCallEvent != null && + (mCurrentCallEvent.getToID() == null || + !mCurrentCallEvent.getToID().contentEquals(to))) + { + mCurrentCallEvent.setToID(to); + broadcast(mCurrentCallEvent); + } + } + } + } - /** - * Broadcasts an update for the FROM group or unit address - */ - private void updateFrom( final String from ) - { - if( from != null && !from.contentEquals( "0000" ) && !from.contentEquals( "000000" ) ) - { - if( mFromTalkgroup == null || !mFromTalkgroup.contentEquals( from )) - { - mFromTalkgroup = from; - broadcast( ChangedAttribute.FROM_TALKGROUP ); - - if( hasAliasList() ) - { - mFromAlias = getAliasList().getTalkgroupAlias( mFromTalkgroup ); - } - else - { - mFromAlias = null; - } - - broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS ); + /** + * Broadcasts an update for the FROM group or unit address + */ + private void updateFrom(final String from) + { + if(from != null && !from.contentEquals("0000") && !from.contentEquals("000000")) + { + if(mFromTalkgroup == null || !mFromTalkgroup.contentEquals(from)) + { + mFromTalkgroup = from; + broadcast(ChangedAttribute.FROM_TALKGROUP); + + if(hasAliasList()) + { + mFromAlias = getAliasList().getTalkgroupAlias(mFromTalkgroup); + } + else + { + mFromAlias = null; + } + + broadcast(ChangedAttribute.FROM_TALKGROUP_ALIAS); /* Send audio metadata update */ - broadcast( new Metadata( MetadataType.FROM, mFromTalkgroup, mFromAlias, true ) ); + broadcast(new Metadata(MetadataType.FROM, mFromTalkgroup, mFromAlias, true)); - if( mCurrentCallEvent != null && - ( mCurrentCallEvent.getFromID() == null || - !mCurrentCallEvent.getFromID().contentEquals( from ) ) ) - { - mCurrentCallEvent.setFromID( from ); - broadcast( mCurrentCallEvent ); - } - } + if(mCurrentCallEvent != null && + (mCurrentCallEvent.getFromID() == null || + !mCurrentCallEvent.getFromID().contentEquals(from))) + { + mCurrentCallEvent.setFromID(from); + broadcast(mCurrentCallEvent); + } + } - } - } - - /** - * Broadcasts an update to the NAC - */ - private void updateNAC( String nac ) - { - if( mNAC == null || ( nac != null && !mNAC.contentEquals( nac ) ) ) - { - mNAC = nac; - - broadcast( ChangedAttribute.NAC ); - } - } - - /** - * Updates the site information - */ - private void updateSite( String site ) - { - if( mSite == null || ( site != null && !mSite.contentEquals( site ) ) ) - { - mSite = site; - - broadcast( ChangedAttribute.SITE ); - - if( hasAliasList() ) - { - Alias alias = getAliasList().getSiteID( mSite ); - - if( alias != null ) - { - mSiteAlias = alias.getName(); - } - else - { - mSiteAlias = null; - } - } + } + } - broadcast( ChangedAttribute.SITE_ALIAS ); - } - } - - private void updateSystem( String system ) - { - if( mSystem == null || ( system != null && !mSystem.contentEquals( system ) ) ) - { - mSystem = system; - - broadcast( ChangedAttribute.SYSTEM ); - } - } - - /** - * Process a unit paging event message - */ - private void processTSBKPage( TSBKMessage message ) - { - switch( message.getOpcode() ) - { - case CALL_ALERT: - CallAlert ca = (CallAlert)message; - - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .from( ca.getSourceID() ) - .to( ca.getTargetAddress() ) - .build() ); - break; - case UNIT_TO_UNIT_ANSWER_REQUEST: - UnitToUnitAnswerRequest utuar = (UnitToUnitAnswerRequest)message; - - if( mLastPageEventID == null || !mLastPageEventID - .contentEquals( utuar.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .details( ( utuar.isEmergency() ? "EMERGENCY" : "" ) ) - .from( utuar.getSourceAddress() ) - .to( utuar.getTargetAddress() ) - .build() ); - - mLastPageEventID = utuar.getTargetAddress(); - } - break; - case SNDCP_DATA_PAGE_REQUEST: - SNDCPDataPageRequest sdpr = (SNDCPDataPageRequest)message; - - if( mLastPageEventID == null || !mLastPageEventID - .contentEquals( sdpr.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .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; + /** + * Broadcasts an update to the NAC + */ + private void updateNAC(String nac) + { + if(mNAC == null || (nac != null && !mNAC.contentEquals(nac))) + { + mNAC = nac; - if( mLastPageEventID == null || !mLastPageEventID - .contentEquals( tiar.getTargetAddress() ) ) - { - broadcast( new P25CallEvent.Builder( CallEventType.PAGE ) - .aliasList( getAliasList() ) - .details( ( "TELEPHONE INTERCONNECT" ) ) - .from( tiar.getTelephoneNumber() ) - .to( tiar.getTargetAddress() ) - .build() ); - - mLastPageEventID = tiar.getTargetAddress(); - } - break; - default: - break; - } - } - - @Override + broadcast(ChangedAttribute.NAC); + } + } + + /** + * Updates the site information + */ + private void updateSite(String site) + { + if(mSite == null || (site != null && !mSite.contentEquals(site))) + { + mSite = site; + + broadcast(ChangedAttribute.SITE); + + if(hasAliasList()) + { + Alias alias = getAliasList().getSiteID(mSite); + + if(alias != null) + { + mSiteAlias = alias.getName(); + } + else + { + mSiteAlias = null; + } + } + + broadcast(ChangedAttribute.SITE_ALIAS); + } + } + + private void updateSystem(String system) + { + if(mSystem == null || (system != null && !mSystem.contentEquals(system))) + { + mSystem = system; + + broadcast(ChangedAttribute.SYSTEM); + } + } + + /** + * Process a unit paging event message + */ + private void processTSBKPage(TSBKMessage message) + { + switch(message.getOpcode()) + { + case CALL_ALERT: + CallAlert ca = (CallAlert) message; + + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .from(ca.getSourceID()) + .to(ca.getTargetAddress()) + .build()); + break; + case UNIT_TO_UNIT_ANSWER_REQUEST: + UnitToUnitAnswerRequest utuar = (UnitToUnitAnswerRequest) message; + + if(mLastPageEventID == null || !mLastPageEventID + .contentEquals(utuar.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .details((utuar.isEmergency() ? "EMERGENCY" : "")) + .from(utuar.getSourceAddress()) + .to(utuar.getTargetAddress()) + .build()); + + mLastPageEventID = utuar.getTargetAddress(); + } + break; + case SNDCP_DATA_PAGE_REQUEST: + SNDCPDataPageRequest sdpr = (SNDCPDataPageRequest) message; + + if(mLastPageEventID == null || !mLastPageEventID + .contentEquals(sdpr.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .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; + + if(mLastPageEventID == null || !mLastPageEventID + .contentEquals(tiar.getTargetAddress())) + { + broadcast(new P25CallEvent.Builder(CallEventType.PAGE) + .aliasList(getAliasList()) + .details(("TELEPHONE INTERCONNECT")) + .from(tiar.getTelephoneNumber()) + .to(tiar.getTargetAddress()) + .build()); + + mLastPageEventID = tiar.getTargetAddress(); + } + break; + default: + break; + } + } + + @Override public String getActivitySummary() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - sb.append( "Activity Summary\n" ); - sb.append( "Decoder:\tP25\n" ); - sb.append( "===================== THIS SITE ======================" ); - - if( mNetworkStatus != null ) - { - sb.append( "\nNAC:\t" + mNetworkStatus.getNAC() ); - sb.append( "\nWACN-SYS:\t" + mNetworkStatus.getWACN() ); - sb.append( "-" + mNetworkStatus.getSystemID() ); - sb.append( " [" ); - sb.append( mNetworkStatus.getNetworkCallsign() ); - sb.append( "]" ); - sb.append( "\nLRA:\t" + mNetworkStatus.getLocationRegistrationArea() ); - } - else if( mNetworkStatusExtended != null ) - { - sb.append( "\nNAC:\t" + mNetworkStatusExtended.getNAC() ); - sb.append( "\nWACN-SYS:\t" + mNetworkStatusExtended.getWACN() ); - sb.append( "-" + mNetworkStatusExtended.getSystemID() ); - sb.append( " [" ); - sb.append( mNetworkStatusExtended.getNetworkCallsign() ); - sb.append( "]" ); - sb.append( "\nLRA:\t" + mNetworkStatusExtended.getLocationRegistrationArea() ); - } + sb.append("Activity Summary\n"); + sb.append("Decoder:\tP25\n"); + sb.append("===================== THIS SITE ======================"); - String site = null; - - if( mRFSSStatusMessage != null ) - { - site = mRFSSStatusMessage.getRFSubsystemID() + "-" + mRFSSStatusMessage.getSiteID(); - } - else if( mRFSSStatusMessageExtended != null ) - { - site = mRFSSStatusMessageExtended.getRFSubsystemID() + "-" + - mRFSSStatusMessageExtended.getSiteID(); - } + if(mNetworkStatus != null) + { + sb.append("\nNAC:\t" + mNetworkStatus.getNAC()); + sb.append("\nWACN-SYS:\t" + mNetworkStatus.getWACN()); + sb.append("-" + mNetworkStatus.getSystemID()); + sb.append(" ["); + sb.append(mNetworkStatus.getNetworkCallsign()); + sb.append("]"); + sb.append("\nLRA:\t" + mNetworkStatus.getLocationRegistrationArea()); + } + else if(mNetworkStatusExtended != null) + { + sb.append("\nNAC:\t" + mNetworkStatusExtended.getNAC()); + sb.append("\nWACN-SYS:\t" + mNetworkStatusExtended.getWACN()); + sb.append("-" + mNetworkStatusExtended.getSystemID()); + sb.append(" ["); + sb.append(mNetworkStatusExtended.getNetworkCallsign()); + sb.append("]"); + sb.append("\nLRA:\t" + mNetworkStatusExtended.getLocationRegistrationArea()); + } - sb.append( "\nRFSS-SITE:\t" + site ); - - if( hasAliasList() ) - { - Alias siteAlias = getAliasList().getSiteID( site ); - - if( siteAlias != null ) - { - sb.append( " " + siteAlias.getName() ); - } - } + String site = null; - if( mNetworkStatus != null ) - { - sb.append( "\nSERVICES:\t" + SystemService.toString( - mNetworkStatus.getSystemServiceClass() ) ); - sb.append( "\nPCCH:\tDNLINK " + mFrequencyFormatter.format( - (double)mNetworkStatus.getDownlinkFrequency() / 1E6d ) + - " [" + mNetworkStatus.getIdentifier() + "-" + - mNetworkStatus.getChannel() + "]\n" ); - sb.append( "\tUPLINK " + mFrequencyFormatter.format( - (double)mNetworkStatus.getUplinkFrequency() / 1E6d ) + " [" + - mNetworkStatus.getIdentifier() + "-" + - mNetworkStatus.getChannel() + "]\n" ); - } - else if( mNetworkStatusExtended != null ) - { - sb.append( "\nSERVICES:\t" + SystemService.toString( - mNetworkStatusExtended.getSystemServiceClass() ) ); - sb.append( "\nPCCH:\tDNLINK " + mFrequencyFormatter.format( - (double)mNetworkStatusExtended.getDownlinkFrequency() / 1E6d ) + - " [" + mNetworkStatusExtended.getTransmitIdentifier() + "-" + - mNetworkStatusExtended.getTransmitChannel() + "]\n" ); - sb.append( "\tUPLINK " + mFrequencyFormatter.format( - (double)mNetworkStatusExtended.getUplinkFrequency() / 1E6d ) + - " [" + mNetworkStatusExtended.getReceiveIdentifier() + "-" + - mNetworkStatusExtended.getReceiveChannel() + "]" ); - } - - if( mSecondaryControlChannels.isEmpty() ) - { - sb.append( "\nSCCH:\tNONE" ); - } - else - { - for( module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast - sec: mSecondaryControlChannels ) - { - sb.append( "\nSCCH:\tDNLINK " + mFrequencyFormatter.format( - (double)sec.getDownlinkFrequency1() / 1E6d ) + - " [" + sec.getIdentifier1() + "-" + sec.getChannel1() + "]\n" ); - sb.append( "\tUPLINK " + mFrequencyFormatter.format( - (double)sec.getUplinkFrequency1() / 1E6d ) + " [" + - sec.getIdentifier1() + "-" + sec.getChannel1() + "]\n" ); - - if( sec.hasChannel2() ) - { - sb.append( "\nSCCH:\tDNLINK " + mFrequencyFormatter.format( - (double)sec.getDownlinkFrequency2() / 1E6d ) + - " [" + sec.getIdentifier2() + "-" + sec.getChannel2() + "]\n" ); - sb.append( "\tUPLINK " + mFrequencyFormatter.format( - (double)sec.getUplinkFrequency2() / 1E6d ) + " [" + - sec.getIdentifier2() + "-" + sec.getChannel2() + "]" ); - } - } - } - - if( mSNDCPDataChannel != null ) - { - sb.append( "\nSNDCP:" ); - sb.append( "\tDNLINK " + mFrequencyFormatter.format( - (double)mSNDCPDataChannel.getDownlinkFrequency()/1E6D ) + - " [" + mSNDCPDataChannel.getTransmitChannel() + "]" ); - sb.append( "\tUPLINK " + mFrequencyFormatter.format( - (double)mSNDCPDataChannel.getUplinkFrequency()/1E6D ) + - " [" + mSNDCPDataChannel.getReceiveChannel() + "]" ); - } - - if( mProtectionParameterBroadcast != null ) - { - sb.append( "\nENCRYPTION:" ); - sb.append( "\nTYPE:\t" + mProtectionParameterBroadcast - .getEncryptionType().name() ); - sb.append( "\nALGORITHM:\t" + mProtectionParameterBroadcast - .getAlgorithmID() ); - sb.append( "\nKEY:\t" + mProtectionParameterBroadcast.getKeyID() ); - sb.append( "\nINBOUND IV:\t" + mProtectionParameterBroadcast - .getInboundInitializationVector() ); - sb.append( "\nOUTBOUND IV:\t" + mProtectionParameterBroadcast - .getOutboundInitializationVector() ); - } - - List identifiers = new ArrayList<>( mBands.keySet() ); - - Collections.sort( identifiers ); - - sb.append( "\nFREQUENCY BANDS:" ); - for( Integer id: identifiers ) - { - IBandIdentifier band = mBands.get( id ); - - sb.append( "\n\t" + id ); - sb.append( "- BASE: " + mFrequencyFormatter.format( - (double)band.getBaseFrequency() / 1E6d ) ); - sb.append( " CHANNEL SIZE: " + mFrequencyFormatter.format( - (double)band.getChannelSpacing() / 1E6d ) ); - sb.append( " UPLINK OFFSET: " + mFrequencyFormatter.format( - (double)band.getTransmitOffset() / 1E6D ) ); - } - - sb.append( "\n\n=================== NEIGHBORS ======================" ); - - if( mNeighborMap.isEmpty() ) - { - sb.append( "\n\tNONE\n" ); - sb.append( "\n----------------------------------------------------" ); - } - else - { - for( IAdjacentSite neighbor: mNeighborMap.values() ) - { - sb.append( "\nNAC:\t" + ((P25Message)neighbor).getNAC() ); - sb.append( "\nSYSTEM:\t" + neighbor.getSystemID() ); - sb.append( "\nLRA:\t" + neighbor.getLRA() ); - - String neighborID = neighbor.getRFSS() + "-" + neighbor.getSiteID(); - sb.append( "\nRFSS-SITE:\t" + neighborID ); - - if( hasAliasList() ) - { - Alias siteAlias = getAliasList().getSiteID( neighborID ); - - if( siteAlias != null ) - { - sb.append( " " + siteAlias.getName() ); - } - } - - sb.append( "\nPCCH:\tDNLINK " + mFrequencyFormatter.format( - (double)neighbor.getDownlinkFrequency() / 1E6d ) + - " [" + neighbor.getDownlinkChannel() + "]" ); - sb.append( "\n\tUPLINK:" + mFrequencyFormatter.format( - (double)neighbor.getUplinkFrequency() / 1E6d ) + - " [" + neighbor.getDownlinkChannel() + "]\n" ); - sb.append( "\nSERVICES:\t" + neighbor.getSystemServiceClass() ); - sb.append( "\n----------------------------------------------------" ); - } - } - - return sb.toString(); + if(mRFSSStatusMessage != null) + { + site = mRFSSStatusMessage.getRFSubsystemID() + "-" + mRFSSStatusMessage.getSiteID(); + } + else if(mRFSSStatusMessageExtended != null) + { + site = mRFSSStatusMessageExtended.getRFSubsystemID() + "-" + + mRFSSStatusMessageExtended.getSiteID(); + } + + sb.append("\nRFSS-SITE:\t" + site); + + if(hasAliasList()) + { + Alias siteAlias = getAliasList().getSiteID(site); + + if(siteAlias != null) + { + sb.append(" " + siteAlias.getName()); + } + } + + if(mNetworkStatus != null) + { + sb.append("\nSERVICES:\t" + SystemService.toString( + mNetworkStatus.getSystemServiceClass())); + sb.append("\nPCCH:\tDNLINK " + mFrequencyFormatter.format( + (double) mNetworkStatus.getDownlinkFrequency() / 1E6d) + + " [" + mNetworkStatus.getIdentifier() + "-" + + mNetworkStatus.getChannel() + "]\n"); + sb.append("\tUPLINK " + mFrequencyFormatter.format( + (double) mNetworkStatus.getUplinkFrequency() / 1E6d) + " [" + + mNetworkStatus.getIdentifier() + "-" + + mNetworkStatus.getChannel() + "]\n"); + } + else if(mNetworkStatusExtended != null) + { + sb.append("\nSERVICES:\t" + SystemService.toString( + mNetworkStatusExtended.getSystemServiceClass())); + sb.append("\nPCCH:\tDNLINK " + mFrequencyFormatter.format( + (double) mNetworkStatusExtended.getDownlinkFrequency() / 1E6d) + + " [" + mNetworkStatusExtended.getTransmitIdentifier() + "-" + + mNetworkStatusExtended.getTransmitChannel() + "]\n"); + sb.append("\tUPLINK " + mFrequencyFormatter.format( + (double) mNetworkStatusExtended.getUplinkFrequency() / 1E6d) + + " [" + mNetworkStatusExtended.getReceiveIdentifier() + "-" + + mNetworkStatusExtended.getReceiveChannel() + "]"); + } + + if(mSecondaryControlChannels.isEmpty()) + { + sb.append("\nSCCH:\tNONE"); + } + else + { + for(module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast + sec : mSecondaryControlChannels) + { + sb.append("\nSCCH:\tDNLINK " + mFrequencyFormatter.format( + (double) sec.getDownlinkFrequency1() / 1E6d) + + " [" + sec.getIdentifier1() + "-" + sec.getChannel1() + "]\n"); + sb.append("\tUPLINK " + mFrequencyFormatter.format( + (double) sec.getUplinkFrequency1() / 1E6d) + " [" + + sec.getIdentifier1() + "-" + sec.getChannel1() + "]\n"); + + if(sec.hasChannel2()) + { + sb.append("\nSCCH:\tDNLINK " + mFrequencyFormatter.format( + (double) sec.getDownlinkFrequency2() / 1E6d) + + " [" + sec.getIdentifier2() + "-" + sec.getChannel2() + "]\n"); + sb.append("\tUPLINK " + mFrequencyFormatter.format( + (double) sec.getUplinkFrequency2() / 1E6d) + " [" + + sec.getIdentifier2() + "-" + sec.getChannel2() + "]"); + } + } + } + + if(mSNDCPDataChannel != null) + { + sb.append("\nSNDCP:"); + sb.append("\tDNLINK " + mFrequencyFormatter.format( + (double) mSNDCPDataChannel.getDownlinkFrequency() / 1E6D) + + " [" + mSNDCPDataChannel.getTransmitChannel() + "]"); + sb.append("\tUPLINK " + mFrequencyFormatter.format( + (double) mSNDCPDataChannel.getUplinkFrequency() / 1E6D) + + " [" + mSNDCPDataChannel.getReceiveChannel() + "]"); + } + + if(mProtectionParameterBroadcast != null) + { + sb.append("\nENCRYPTION:"); + sb.append("\nTYPE:\t" + mProtectionParameterBroadcast + .getEncryptionType().name()); + sb.append("\nALGORITHM:\t" + mProtectionParameterBroadcast + .getAlgorithmID()); + sb.append("\nKEY:\t" + mProtectionParameterBroadcast.getKeyID()); + sb.append("\nINBOUND IV:\t" + mProtectionParameterBroadcast + .getInboundInitializationVector()); + sb.append("\nOUTBOUND IV:\t" + mProtectionParameterBroadcast + .getOutboundInitializationVector()); + } + + List identifiers = new ArrayList<>(mBands.keySet()); + + Collections.sort(identifiers); + + sb.append("\nFREQUENCY BANDS:"); + for(Integer id : identifiers) + { + IBandIdentifier band = mBands.get(id); + + sb.append("\n\t" + id); + sb.append("- BASE: " + mFrequencyFormatter.format( + (double) band.getBaseFrequency() / 1E6d)); + sb.append(" CHANNEL SIZE: " + mFrequencyFormatter.format( + (double) band.getChannelSpacing() / 1E6d)); + sb.append(" UPLINK OFFSET: " + mFrequencyFormatter.format( + (double) band.getTransmitOffset() / 1E6D)); + } + + sb.append("\n\n=================== NEIGHBORS ======================"); + + if(mNeighborMap.isEmpty()) + { + sb.append("\n\tNONE\n"); + sb.append("\n----------------------------------------------------"); + } + else + { + for(IAdjacentSite neighbor : mNeighborMap.values()) + { + sb.append("\nNAC:\t" + ((P25Message) neighbor).getNAC()); + sb.append("\nSYSTEM:\t" + neighbor.getSystemID()); + sb.append("\nLRA:\t" + neighbor.getLRA()); + + String neighborID = neighbor.getRFSS() + "-" + neighbor.getSiteID(); + sb.append("\nRFSS-SITE:\t" + neighborID); + + if(hasAliasList()) + { + Alias siteAlias = getAliasList().getSiteID(neighborID); + + if(siteAlias != null) + { + sb.append(" " + siteAlias.getName()); + } + } + + sb.append("\nPCCH:\tDNLINK " + mFrequencyFormatter.format( + (double) neighbor.getDownlinkFrequency() / 1E6d) + + " [" + neighbor.getDownlinkChannel() + "]"); + sb.append("\n\tUPLINK:" + mFrequencyFormatter.format( + (double) neighbor.getUplinkFrequency() / 1E6d) + + " [" + neighbor.getDownlinkChannel() + "]\n"); + sb.append("\nSERVICES:\t" + neighbor.getSystemServiceClass()); + sb.append("\n----------------------------------------------------"); + } + } + + return sb.toString(); } - - public String getNAC() - { - return mNAC; - } - - public String getSystem() - { - return mSystem; - } - - public String getSiteAlias() - { - return mSiteAlias; - } - - public String getSite() - { - return mSite; - } - - public String getFromTalkgroup() - { - return mFromTalkgroup; - } - - public Alias getFromAlias() - { - return mFromAlias; - } - - public String getToTalkgroup() - { - return mToTalkgroup; - } - - public Alias getToAlias() - { - return mToAlias; - } - - @Override - public void receiveDecoderStateEvent( DecoderStateEvent event ) - { - switch( event.getEvent() ) - { - case RESET: - resetState(); - break; - case SOURCE_FREQUENCY: - mCurrentChannelFrequency = event.getFrequency(); - break; - case TRAFFIC_CHANNEL_ALLOCATION: - if( event.getSource() != P25DecoderState.this ) - { - if( event instanceof TrafficChannelAllocationEvent ) - { - TrafficChannelAllocationEvent allocationEvent = - (TrafficChannelAllocationEvent)event; - - mCurrentCallEvent = (P25CallEvent)allocationEvent.getCallEvent(); - mCurrentChannel = allocationEvent.getCallEvent().getChannel(); - broadcast( ChangedAttribute.CHANNEL_NUMBER ); - - mCurrentChannelFrequency = allocationEvent.getCallEvent().getFrequency(); - broadcast( ChangedAttribute.SOURCE ); + public String getNAC() + { + return mNAC; + } - mFromTalkgroup = allocationEvent.getCallEvent().getFromID(); - broadcast( ChangedAttribute.FROM_TALKGROUP ); - - mToTalkgroup = allocationEvent.getCallEvent().getToID(); - broadcast( ChangedAttribute.TO_TALKGROUP ); + public String getSystem() + { + return mSystem; + } - if( allocationEvent.getCallEvent().hasAliasList() ) - { - AliasList aliasList = allocationEvent.getCallEvent().getAliasList(); - - mFromAlias = aliasList.getTalkgroupAlias( mFromTalkgroup ); - broadcast( ChangedAttribute.FROM_TALKGROUP_ALIAS ); - - mToAlias = aliasList.getTalkgroupAlias( mToTalkgroup ); - broadcast( ChangedAttribute.TO_TALKGROUP_ALIAS ); - } - } - } - break; - default: - break; - } - } + public String getSiteAlias() + { + return mSiteAlias; + } - @Override - public void start( ScheduledExecutorService executor ) - { - //Change the default (45-second) traffic channel timeout to 1 second - if( mChannelType == ChannelType.TRAFFIC ) - { - broadcast( new ChangeChannelTimeoutEvent( this, ChannelType.TRAFFIC, 1000 ) ); - } - } + public String getSite() + { + return mSite; + } + + public String getFromTalkgroup() + { + return mFromTalkgroup; + } + + public Alias getFromAlias() + { + return mFromAlias; + } + + public String getToTalkgroup() + { + return mToTalkgroup; + } + + public Alias getToAlias() + { + return mToAlias; + } + + @Override + public void receiveDecoderStateEvent(DecoderStateEvent event) + { + switch(event.getEvent()) + { + case RESET: + resetState(); + break; + case SOURCE_FREQUENCY: + mCurrentChannelFrequency = event.getFrequency(); + break; + case TRAFFIC_CHANNEL_ALLOCATION: + if(event.getSource() != P25DecoderState.this) + { + if(event instanceof TrafficChannelAllocationEvent) + { + TrafficChannelAllocationEvent allocationEvent = + (TrafficChannelAllocationEvent) event; + + mCurrentCallEvent = (P25CallEvent) allocationEvent.getCallEvent(); + + mCurrentChannel = allocationEvent.getCallEvent().getChannel(); + broadcast(ChangedAttribute.CHANNEL_NUMBER); + + mCurrentChannelFrequency = allocationEvent.getCallEvent().getFrequency(); + broadcast(ChangedAttribute.SOURCE); + + mFromTalkgroup = allocationEvent.getCallEvent().getFromID(); + broadcast(ChangedAttribute.FROM_TALKGROUP); + + mToTalkgroup = allocationEvent.getCallEvent().getToID(); + broadcast(ChangedAttribute.TO_TALKGROUP); + + if(allocationEvent.getCallEvent().hasAliasList()) + { + AliasList aliasList = allocationEvent.getCallEvent().getAliasList(); + + mFromAlias = aliasList.getTalkgroupAlias(mFromTalkgroup); + broadcast(ChangedAttribute.FROM_TALKGROUP_ALIAS); + + mToAlias = aliasList.getTalkgroupAlias(mToTalkgroup); + broadcast(ChangedAttribute.TO_TALKGROUP_ALIAS); + } + } + } + break; + default: + break; + } + } + + @Override + public void start(ScheduledExecutorService executor) + { + //Change the default (45-second) traffic channel timeout to 1 second + if(mChannelType == ChannelType.TRAFFIC) + { + broadcast(new ChangeChannelTimeoutEvent(this, ChannelType.TRAFFIC, 1000)); + } + } } diff --git a/src/module/decode/p25/message/tsbk/motorola/PatchGroup.java b/src/module/decode/p25/message/tsbk/motorola/PatchGroup.java index f5c5305a..8d8c446d 100644 --- a/src/module/decode/p25/message/tsbk/motorola/PatchGroup.java +++ b/src/module/decode/p25/message/tsbk/motorola/PatchGroup.java @@ -4,6 +4,10 @@ import module.decode.p25.reference.DataUnitID; import alias.AliasList; import bits.BinaryMessage; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public abstract class PatchGroup extends MotorolaTSBKMessage { public static final int[] PATCH_GROUP_ADDRESS = { 80,81,82,83,84,85,86,87, @@ -15,6 +19,8 @@ public abstract class PatchGroup extends MotorolaTSBKMessage public static final int[] GROUP_ADDRESS_3 = { 128,129,130,131,132,133,134, 135,136,137,138,139,140,141,142,143 }; + private List mPatchedTalkgroups; + public PatchGroup( BinaryMessage message, DataUnitID duid, AliasList aliasList ) { @@ -28,15 +34,11 @@ public abstract class PatchGroup extends MotorolaTSBKMessage sb.append( getMessageStub() ); - sb.append( " PATCH GROUP:" ); + sb.append( " " ); sb.append( getPatchGroupAddress() ); - sb.append( " GRP1:" ); - sb.append( getGroupAddress1() ); - sb.append( " GRP2:" ); - sb.append( getGroupAddress2() ); - sb.append( " GRP3:" ); - sb.append( getGroupAddress3() ); - + sb.append(" "); + sb.append(getPatchedTalkgroups()); + return sb.toString(); } @@ -44,6 +46,35 @@ public abstract class PatchGroup extends MotorolaTSBKMessage { return mMessage.getHex( PATCH_GROUP_ADDRESS, 4 ); } + + /** + * List of de-deplicated patched talkgroups contained in this message + */ + public List getPatchedTalkgroups() + { + if(mPatchedTalkgroups == null) + { + mPatchedTalkgroups = new ArrayList<>(); + + mPatchedTalkgroups.add(getGroupAddress1()); + + String group2 = getGroupAddress2(); + + if(!mPatchedTalkgroups.contains(group2)) + { + mPatchedTalkgroups.add(group2); + } + + String group3 = getGroupAddress3(); + + if(!mPatchedTalkgroups.contains(group3)) + { + mPatchedTalkgroups.add(group3); + } + } + + return mPatchedTalkgroups; + } public String getGroupAddress1() {